diff options
226 files changed, 9288 insertions, 6240 deletions
diff --git a/.gitignore b/.gitignore index 4c8c8e4115..60e5002bd5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ GIT-CFLAGS GIT-VERSION-FILE git git-add +git-add--interactive git-am git-annotate git-apply @@ -10,6 +11,7 @@ git-applypatch git-archimport git-archive git-bisect +git-blame git-branch git-cat-file git-check-ref-format @@ -60,13 +62,13 @@ git-mailsplit git-merge git-merge-base git-merge-index +git-merge-file git-merge-tree git-merge-octopus git-merge-one-file git-merge-ours git-merge-recur git-merge-recursive -git-merge-recursive-old git-merge-resolve git-merge-stupid git-mktag @@ -87,6 +89,7 @@ git-quiltimport git-read-tree git-rebase git-receive-pack +git-reflog git-relink git-repack git-repo-config @@ -152,4 +155,3 @@ config.status config.mak.autogen config.mak.append configure -git-blame diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000000..2c658f42f5 --- /dev/null +++ b/.mailmap @@ -0,0 +1,37 @@ +# +# This list is used by git-shortlog to fix a few botched name translations +# in the git archive, either because the author's full name was messed up +# and/or not always written the same way, making contributions from the +# same person appearing not to be so. +# + +Aneesh Kumar K.V <aneesh.kumar@gmail.com> +Chris Shoemaker <c.shoemaker@cox.net> +Daniel Barkalow <barkalow@iabervon.org> +David KÃ¥gedal <davidk@lysator.liu.se> +Fredrik Kuivinen <freku045@student.liu.se> +H. Peter Anvin <hpa@bonde.sc.orionmulti.com> +H. Peter Anvin <hpa@tazenda.sc.orionmulti.com> +H. Peter Anvin <hpa@trantor.hos.anvin.org> +Horst H. von Brand <vonbrand@inf.utfsm.cl> +Joachim Berdal Haga <cjhaga@fys.uio.no> +Jon Loeliger <jdl@freescale.com> +Jon Seymour <jon@blackcubes.dyndns.org> +Karl Hasselström <kha@treskal.com> +Kent Engstrom <kent@lysator.liu.se> +Lars Doelle <lars.doelle@on-line.de> +Lars Doelle <lars.doelle@on-line ! de> +Lukas Sandström <lukass@etek.chalmers.se> +Martin Langhoff <martin@catalyst.net.nz> +Nguyá»…n Thái Ngá»c Duy <pclouds@gmail.com> +Ramsay Allan Jones <ramsay@ramsay1.demon.co.uk> +René Scharfe <rene.scharfe@lsrfire.ath.cx> +Robert Fitzsimons <robfitz@273k.net> +Santi Béjar <sbejar@gmail.com> +Sean Estabrooks <seanlkml@sympatico.ca> +Shawn O. Pearce <spearce@spearce.org> +Tony Luck <tony.luck@intel.com> +Ville Skyttä <scop@xemacs.org> +YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org> +anonymous <linux@horizon.com> +anonymous <linux@horizon.net> diff --git a/Documentation/Makefile b/Documentation/Makefile index c00f5f62b7..93c7024b48 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -32,6 +32,7 @@ man7dir=$(mandir)/man7 # DESTDIR= INSTALL?=install +DOC_REF = origin/man -include ../config.mak.autogen @@ -56,8 +57,8 @@ man7: $(DOC_MAN7) install: man $(INSTALL) -d -m755 $(DESTDIR)$(man1dir) $(DESTDIR)$(man7dir) - $(INSTALL) $(DOC_MAN1) $(DESTDIR)$(man1dir) - $(INSTALL) $(DOC_MAN7) $(DESTDIR)$(man7dir) + $(INSTALL) -m644 $(DOC_MAN1) $(DESTDIR)$(man1dir) + $(INSTALL) -m644 $(DOC_MAN7) $(DESTDIR)$(man7dir) # @@ -112,3 +113,6 @@ $(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt install-webdoc : html sh ./install-webdoc.sh $(WEBDOC_DEST) + +quick-install: + sh ./install-doc-quick.sh $(DOC_REF) $(mandir) diff --git a/Documentation/callouts.xsl b/Documentation/callouts.xsl index ad03755d8f..6a361a2136 100644 --- a/Documentation/callouts.xsl +++ b/Documentation/callouts.xsl @@ -13,4 +13,18 @@ <xsl:apply-templates/> <xsl:text>.br </xsl:text> </xsl:template> + +<!-- sorry, this is not about callouts, but attempts to work around + spurious .sp at the tail of the line docbook stylesheets seem to add --> +<xsl:template match="simpara"> + <xsl:variable name="content"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:value-of select="normalize-space($content)"/> + <xsl:if test="not(ancestor::authorblurb) and + not(ancestor::personblurb)"> + <xsl:text> </xsl:text> + </xsl:if> +</xsl:template> + </xsl:stylesheet> diff --git a/Documentation/config.txt b/Documentation/config.txt index 9090762819..22482d6a94 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -31,6 +31,11 @@ Example external = "/usr/local/bin/gnu-diff -u" renames = true + [branch "devel"] + remote = origin + merge = refs/heads/devel + + Variables ~~~~~~~~~ @@ -79,8 +84,11 @@ core.logAllRefUpdates:: file is automatically created for branch heads. This information can be used to determine what commit - was the tip of a branch "2 days ago". This value is - false by default (no automated creation of log files). + was the tip of a branch "2 days ago". + + This value is true by default in a repository that has + a working directory associated with it, and false by + default in a bare repository. core.repositoryFormatVersion:: Internal variable identifying the repository format and layout @@ -125,21 +133,24 @@ apply.whitespace:: branch.<name>.remote:: When in branch <name>, it tells `git fetch` which remote to fetch. + If this option is not given, `git fetch` defaults to remote "origin". branch.<name>.merge:: - When in branch <name>, it tells `git fetch` the default remote branch - to be merged. - -pager.color:: - A boolean to enable/disable colored output when the pager is in - use (default is true). - -diff.color:: + When in branch <name>, it tells `git fetch` the default refspec to + be marked for merging in FETCH_HEAD. The value has exactly to match + a remote part of one of the refspecs which are fetched from the remote + given by "branch.<name>.remote". + The merge information is used by `git pull` (which at first calls + `git fetch`) to lookup the default branch for merging. Without + this option, `git pull` defaults to merge the first refspec fetched. + Specify multiple values to get an octopus merge. + +color.diff:: When true (or `always`), always use colors in patch. When false (or `never`), never. When set to `auto`, use colors only when the output is to the terminal. -diff.color.<slot>:: +color.diff.<slot>:: Use customized color for diff colorization. `<slot>` specifies which part of the patch to use the specified color, and is one of `plain` (context text), `meta` @@ -150,6 +161,24 @@ diff.color.<slot>:: `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, or `white`. +color.pager:: + A boolean to enable/disable colored output when the pager is in + use (default is true). + +color.status:: + A boolean to enable/disable color in the output of + gitlink:git-status[1]. May be set to `true` (or `always`), + `false` (or `never`) or `auto`, in which case colors are used + only when the output is to a terminal. Defaults to false. + +color.status.<slot>:: + Use customized color for status colorization. `<slot>` is + 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.diff.<slot>. + diff.renameLimit:: The number of files to consider when performing the copy/rename detection; equivalent to the git diff option '-l'. @@ -264,20 +293,6 @@ showbranch.default:: The default set of branches for gitlink:git-show-branch[1]. See gitlink:git-show-branch[1]. -status.color:: - A boolean to enable/disable color in the output of - gitlink:git-status[1]. May be set to `true` (or `always`), - `false` (or `never`) or `auto`, in which case colors are used - only when the output is to a terminal. Defaults to false. - -status.color.<slot>:: - Use customized color for status colorization. `<slot>` is - one of `header` (the header text of the status message), - `updated` (files which are updated but not committed), - `changed` (files which are changed but not updated in the index), - or `untracked` (files which are not tracked by git). The values of - these variables may be specified as in diff.color.<slot>. - tar.umask:: By default, gitlink:git-tar-tree[1] sets file and directories modes to 0666 or 0777. While this is both useful and acceptable for projects diff --git a/Documentation/core-tutorial.txt b/Documentation/core-tutorial.txt index 47505aa20a..5ea611748c 100644 --- a/Documentation/core-tutorial.txt +++ b/Documentation/core-tutorial.txt @@ -57,7 +57,7 @@ $ git-init-db to which git will reply ---------------- -defaulting to local storage area +Initialized empty Git repository in .git/ ---------------- which is just git's way of saying that you haven't been doing anything @@ -336,17 +336,9 @@ $ commit=$(echo 'Initial commit' | git-commit-tree $tree) $ git-update-ref HEAD $commit ------------------------------------------------ -which will say: - ----------------- -Committing initial tree 8988da15d077d4829fc51d8544c097def6644dbb ----------------- - -just to warn you about the fact that it created a totally new commit -that is not related to anything else. Normally you do this only *once* -for a project ever, and all later commits will be parented on top of an -earlier commit, and you'll never see this "Committing initial tree" -message ever again. +In this case this creates a totally new commit that is not related to +anything else. Normally you do this only *once* for a project ever, and +all later commits will be parented on top of an earlier commit. Again, normally you'd never actually do this by hand. There is a helpful script called `git commit` that will do all of this for you. So diff --git a/Documentation/cvs-migration.txt b/Documentation/cvs-migration.txt index 6812683a16..b657f4589f 100644 --- a/Documentation/cvs-migration.txt +++ b/Documentation/cvs-migration.txt @@ -1,113 +1,21 @@ git for CVS users ================= -So you're a CVS user. That's OK, it's a treatable condition. The job of -this document is to put you on the road to recovery, by helping you -convert an existing cvs repository to git, and by showing you how to use a -git repository in a cvs-like fashion. +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] should be sufficient. -First, note some ways that git differs from CVS: +Developing against a shared repository +-------------------------------------- - * Commits are atomic and project-wide, not per-file as in CVS. - - * Offline work is supported: you can make multiple commits locally, - then submit them when you're ready. - - * Branching is fast and easy. - - * 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; see below - for details. - -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. The magic command line is then - -------------------------------------------- -$ git cvsimport -v -d <cvsroot> -C <destination> <module> -------------------------------------------- - -This puts a git archive of the named CVS module in the directory -<destination>, which will be created if necessary. The -v option makes -the conversion script very chatty. - -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. - -Development Models ------------------- - -CVS users are accustomed to giving a group of developers commit access to -a common repository. In the next section we'll explain how to do this -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. - -Emulating the CVS Development Model ------------------------------------ - -Start with an ordinary git working directory containing the project, and -remove the checked-out files, keeping just the bare .git directory: - ------------------------------------------------- -$ mv project/.git /pub/repo.git -$ rm -r project/ ------------------------------------------------- - -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 gitlink:git-shell[1]. - -Put all the committers in the same group, and make the repository -writable by that group: - ------------------------------------------------- -$ chgrp -R $group repo.git -$ find repo.git -mindepth 1 -type d |xargs chmod ug+rwx,g+s -$ GIT_DIR=repo.git git repo-config core.sharedrepository true ------------------------------------------------- - -Make sure committers have a umask of at most 027, so that the directories -they create are writable and searchable by other group members. - -Suppose this repository is now set up in /pub/repo.git on the host +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: +repository over ssh with: ------------------------------------------------ $ git clone foo.com:/pub/repo.git/ my-project @@ -121,7 +29,8 @@ $ git pull origin ------------------------------------------------ which merges in any work that others might have done since the clone -operation. +operation. If there are uncommitted changes in your working tree, commit +them first before running git pull. [NOTE] ================================ @@ -129,20 +38,22 @@ The first `git clone` places the following in the `my-project/.git/remotes/origin` file, and that's why the previous step and the next step both work. ------------ -URL: foo.com:/pub/project.git/ my-project -Pull: master:origin +URL: foo.com:/pub/project.git/ +Pull: refs/heads/master:refs/remotes/origin/master ------------ ================================ -You can update the shared repository with your changes using: +You can update the shared repository with your changes by first committing +your changes, and then using the gitlink:git-push[1] command: ------------------------------------------------ $ git push origin master ------------------------------------------------ -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. +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 @@ -151,21 +62,77 @@ in the local repository. So the last `push` can be done with either of: ------------ $ git push origin -$ git push repo.shared.xz:/pub/scm/project.git/ +$ git push foo.com:/pub/project.git/ ------------ as long as the shared repository does not have any branches other than `master`. -[NOTE] -============ -Because of this behavior, if the shared repository and the developer's -repository both have branches named `origin`, then a push like the above -attempts to update the `origin` branch in the shared repository from the -developer's `origin` branch. The results may be unexpected, so it's -usually best to remove any branch named `origin` from the shared -repository. -============ +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-db --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 gitlink: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 gitlink:git-cvsimport[1]: + +------------------------------------------- +$ git cvsimport -C <destination> +------------------------------------------- + +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 ------------------------------------- @@ -178,127 +145,30 @@ You can enforce finer grained permissions using update hooks. See link:howto/update-hook-example.txt[Controlling access to branches using update hooks]. -CVS annotate ------------- +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 gitlink: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. -So, something has gone wrong, and you don't know whom to blame, and -you're an ex-CVS user and used to do "cvs annotate" to see who caused -the breakage. You're looking for the "git annotate", and it's just -claiming not to find such a script. You're annoyed. - -Yes, that's right. Core git doesn't do "annotate", although it's -technically possible, and there are at least two specialized scripts out -there that can be used to get equivalent information (see the git -mailing list archives for details). - -git has a couple of alternatives, though, that you may find sufficient -or even superior depending on your use. One is called "git-whatchanged" -(for obvious reasons) and the other one is called "pickaxe" ("a tool for -the software archaeologist"). - -The "git-whatchanged" script is a truly trivial script that can give you -a good overview of what has changed in a file or a directory (or an -arbitrary list of files or directories). The "pickaxe" support is an -additional layer that can be used to further specify exactly what you're -looking for, if you already know the specific area that changed. - -Let's step back a bit and think about the reason why you would -want to do "cvs annotate a-file.c" to begin with. - -You would use "cvs annotate" on a file when you have trouble -with a function (or even a single "if" statement in a function) -that happens to be defined in the file, which does not do what -you want it to do. And you would want to find out why it was -written that way, because you are about to modify it to suit -your needs, and at the same time you do not want to break its -current callers. For that, you are trying to find out why the -original author did things that way in the original context. - -Many times, it may be enough to see the commit log messages of -commits that touch the file in question, possibly along with the -patches themselves, like this: - - $ git-whatchanged -p a-file.c - -This will show log messages and patches for each commit that -touches a-file. - -This, however, may not be very useful when this file has many -modifications that are not related to the piece of code you are -interested in. You would see many log messages and patches that -do not have anything to do with the piece of code you are -interested in. As an example, assuming that you have this piece -of code that you are interested in in the HEAD version: - - if (frotz) { - nitfol(); - } - -you would use git-rev-list and git-diff-tree like this: - - $ git-rev-list HEAD | - git-diff-tree --stdin -v -p -S'if (frotz) { - nitfol(); - }' - -We have already talked about the "\--stdin" form of git-diff-tree -command that reads the list of commits and compares each commit -with its parents (otherwise you should go back and read the tutorial). -The git-whatchanged command internally runs -the equivalent of the above command, and can be used like this: - - $ git-whatchanged -p -S'if (frotz) { - nitfol(); - }' - -When the -S option is used, git-diff-tree command outputs -differences between two commits only if one tree has the -specified string in a file and the corresponding file in the -other tree does not. The above example looks for a commit that -has the "if" statement in it in a file, but its parent commit -does not have it in the same shape in the corresponding file (or -the other way around, where the parent has it and the commit -does not), and the differences between them are shown, along -with the commit message (thanks to the -v flag). It does not -show anything for commits that do not touch this "if" statement. - -Also, in the original context, the same statement might have -appeared at first in a different file and later the file was -renamed to "a-file.c". CVS annotate would not help you to go -back across such a rename, but git would still help you in such -a situation. For that, you can give the -C flag to -git-diff-tree, like this: - - $ git-whatchanged -p -C -S'if (frotz) { - nitfol(); - }' - -When the -C flag is used, file renames and copies are followed. -So if the "if" statement in question happens to be in "a-file.c" -in the current HEAD commit, even if the file was originally -called "o-file.c" and then renamed in an earlier commit, or if -the file was created by copying an existing "o-file.c" in an -earlier commit, you will not lose track. If the "if" statement -did not change across such a rename or copy, then the commit that -does rename or copy would not show in the output, and if the -"if" statement was modified while the file was still called -"o-file.c", it would find the commit that changed the statement -when it was in "o-file.c". - -NOTE: The current version of "git-diff-tree -C" is not eager - enough to find copies, and it will miss the fact that a-file.c - was created by copying o-file.c unless o-file.c was somehow - changed in the same commit. - -You can use the --pickaxe-all flag in addition to the -S flag. -This causes the differences from all the files contained in -those two commits, not just the differences between the files -that contain this changed "if" statement: - - $ git-whatchanged -p -C -S'if (frotz) { - nitfol(); - }' --pickaxe-all - -NOTE: This option is called "--pickaxe-all" because -S - option is internally called "pickaxe", a tool for software - archaeologists. +With a small group, developers may just pull changes from each other's +repositories without the need for a central maintainer. diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt index e4520e28e5..883c1bb0a6 100644 --- a/Documentation/diff-format.txt +++ b/Documentation/diff-format.txt @@ -65,62 +65,17 @@ Generating patches with -p When "git-diff-index", "git-diff-tree", or "git-diff-files" are run with a '-p' option, they do not produce the output described above; -instead they produce a patch file. +instead they produce a patch file. You can customize the creation +of such patches via the GIT_EXTERNAL_DIFF and the GIT_DIFF_OPTS +environment variables. -The patch generation can be customized at two levels. - -1. When the environment variable 'GIT_EXTERNAL_DIFF' is not set, - these commands internally invoke "diff" like this: - - diff -L a/<path> -L b/<path> -pu <old> <new> -+ -For added files, `/dev/null` is used for <old>. For removed -files, `/dev/null` is used for <new> -+ -The "diff" formatting options can be customized via the -environment variable 'GIT_DIFF_OPTS'. For example, if you -prefer context diff: - - GIT_DIFF_OPTS=-c git-diff-index -p HEAD - - -2. When the environment variable 'GIT_EXTERNAL_DIFF' is set, the - program named by it is called, instead of the diff invocation - described above. -+ -For a path that is added, removed, or modified, -'GIT_EXTERNAL_DIFF' is called with 7 parameters: - - path old-file old-hex old-mode new-file new-hex new-mode -+ -where: - - <old|new>-file:: are files GIT_EXTERNAL_DIFF can use to read the - contents of <old|new>, - <old|new>-hex:: are the 40-hexdigit SHA1 hashes, - <old|new>-mode:: are the octal representation of the file modes. - -+ -The file parameters can point at the user's working file -(e.g. `new-file` in "git-diff-files"), `/dev/null` (e.g. `old-file` -when a new file is added), or a temporary file (e.g. `old-file` in the -index). 'GIT_EXTERNAL_DIFF' should not worry about unlinking the -temporary file --- it is removed when 'GIT_EXTERNAL_DIFF' exits. - -For a path that is unmerged, 'GIT_EXTERNAL_DIFF' is called with 1 -parameter, <path>. - - -git specific extension to diff format -------------------------------------- - -What -p option produces is slightly different from the -traditional diff format. +What the -p option produces is slightly different from the traditional +diff format. 1. It is preceded with a "git diff" header, that looks like this: - diff --git a/file1 b/file2 + diff --git a/file1 b/file2 + The `a/` and `b/` filenames are the same unless rename/copy is involved. Especially, even for a creation or a deletion, diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index e112172ca5..da1cc60e97 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -19,7 +19,14 @@ --numstat:: Similar to \--stat, but shows number of added and deleted lines in decimal notation and pathname without - abbreviation, to make it more machine friendly. + abbreviation, to make it more machine friendly. For + binary files, outputs two `-` instead of saying + `0 0`. + +--shortstat:: + Output only the last line of the --stat format containing total + number of modified files, as well as number of added and deleted + lines. --summary:: Output a condensed summary of extended header information @@ -129,5 +136,21 @@ -a:: Shorthand for "--text". +--ignore-space-change:: + Ignore changes in amount of white space. This ignores white + space at line end, and consider all other sequences of one or + more white space characters to be equivalent. + +-b:: + Shorthand for "--ignore-space-change". + +--ignore-all-space:: + Ignore white space when comparing lines. This ignores + difference even if one line has white space where the other + line has none. + +-w:: + Shorthand for "--ignore-all-space". + For more detailed explanation on these common options, see also link:diffcore.html[diffcore documentation]. diff --git a/Documentation/everyday.txt b/Documentation/everyday.txt index 9677671892..5d17ace721 100644 --- a/Documentation/everyday.txt +++ b/Documentation/everyday.txt @@ -47,11 +47,11 @@ $ git repack <3> $ git prune <4> ------------ + -<1> running without "--full" is usually cheap and assures the +<1> running without `\--full` is usually cheap and assures the repository health reasonably well. <2> check how many loose objects there are and how much disk space is wasted by not repacking. -<3> without "-a" repacks incrementally. repacking every 4-5MB +<3> without `-a` repacks incrementally. repacking every 4-5MB of loose objects accumulation may be a good rule of thumb. <4> after repack, prune removes the duplicate loose objects. @@ -80,8 +80,7 @@ following commands. * gitlink:git-checkout[1] and gitlink:git-branch[1] to switch branches. - * gitlink:git-add[1] and gitlink:git-update-index[1] to manage - the index file. + * gitlink:git-add[1] to manage the index file. * gitlink:git-diff[1] and gitlink:git-status[1] to see what you are in the middle of doing. @@ -91,8 +90,7 @@ following commands. * gitlink:git-reset[1] and gitlink:git-checkout[1] (with pathname parameters) to undo changes. - * gitlink:git-pull[1] with "." as the remote to merge between - local branches. + * gitlink:git-merge[1] to merge between local branches. * gitlink:git-rebase[1] to maintain topic branches. @@ -101,7 +99,7 @@ following commands. Examples ~~~~~~~~ -Use a tarball as a starting point for a new repository: +Use a tarball as a starting point for a new repository.:: + ------------ $ tar zxf frotz.tar.gz @@ -123,7 +121,7 @@ $ edit/compile/test $ git checkout -- curses/ux_audio_oss.c <2> $ git add curses/ux_audio_alsa.c <3> $ edit/compile/test -$ git diff <4> +$ git diff HEAD <4> $ git commit -a -s <5> $ edit/compile/test $ git reset --soft HEAD^ <6> @@ -131,15 +129,15 @@ $ edit/compile/test $ git diff ORIG_HEAD <7> $ git commit -a -c ORIG_HEAD <8> $ git checkout master <9> -$ git pull . alsa-audio <10> +$ git merge alsa-audio <10> $ git log --since='3 days ago' <11> $ git log v2.43.. curses/ <12> ------------ + <1> create a new topic branch. -<2> revert your botched changes in "curses/ux_audio_oss.c". +<2> revert your botched changes in `curses/ux_audio_oss.c`. <3> you need to tell git if you added a new file; removal and -modification will be caught if you do "commit -a" later. +modification will be caught if you do `git commit -a` later. <4> to see what changes you are committing. <5> commit everything as you have tested, with your sign-off. <6> take the last commit back, keeping what is in the working tree. @@ -147,11 +145,13 @@ modification will be caught if you do "commit -a" later. <8> redo the commit undone in the previous step, using the message you originally wrote. <9> switch to the master branch. -<10> merge a topic branch into your master branch +<10> merge a topic branch into your master branch. You can also use +`git pull . alsa-audio`, i.e. pull from the local repository. <11> review commit logs; other forms to limit output can be -combined and include --max-count=10 (show 10 commits), --until='2005-12-10'. -<12> view only the changes that touch what's in curses/ -directory, since v2.43 tag. +combined and include `\--max-count=10` (show 10 commits), +`\--until=2005-12-10`, etc. +<12> view only the changes that touch what's in `curses/` +directory, since `v2.43` tag. Individual Developer (Participant)[[Individual Developer (Participant)]] @@ -193,7 +193,7 @@ $ git fetch --tags <8> + <1> repeat as needed. <2> extract patches from your branch for e-mail submission. -<3> "pull" fetches from "origin" by default and merges into the +<3> `git pull` fetches from `origin` by default and merges into the current branch. <4> immediately after pulling, look at the changes done upstream since last time we checked, only in the @@ -201,37 +201,41 @@ area we are interested in. <5> fetch from a specific branch from a specific repository and merge. <6> revert the pull. <7> garbage collect leftover objects from reverted pull. -<8> from time to time, obtain official tags from the "origin" -and store them under .git/refs/tags/. +<8> from time to time, obtain official tags from the `origin` +and store them under `.git/refs/tags/`. Push into another repository.:: + ------------ -satellite$ git clone mothership:frotz/.git frotz <1> +satellite$ git clone mothership:frotz frotz <1> satellite$ cd frotz -satellite$ cat .git/remotes/origin <2> -URL: mothership:frotz/.git -Pull: master:origin -satellite$ echo 'Push: master:satellite' >>.git/remotes/origin <3> +satellite$ git repo-config --get-regexp '^(remote|branch)\.' <2> +remote.origin.url mothership:frotz +remote.origin.fetch refs/heads/*:refs/remotes/origin/* +branch.master.remote origin +branch.master.merge refs/heads/master +satellite$ git repo-config remote.origin.push \ + master:refs/remotes/satellite/master <3> satellite$ edit/compile/test/commit satellite$ git push origin <4> mothership$ cd frotz mothership$ git checkout master -mothership$ git pull . satellite <5> +mothership$ git merge satellite/master <5> ------------ + <1> mothership machine has a frotz repository under your home directory; clone from it to start a repository on the satellite machine. -<2> clone creates this file by default. It arranges "git pull" -to fetch and store the master branch head of mothership machine -to local "origin" branch. -<3> arrange "git push" to push local "master" branch to -"satellite" branch of the mothership machine. -<4> push will stash our work away on "satellite" branch on the -mothership machine. You could use this as a back-up method. +<2> clone sets these configuration variables by default. +It arranges `git pull` to fetch and store the branches of mothership +machine to local `remotes/origin/*` tracking branches. +<3> arrange `git push` to push local `master` branch to +`remotes/satellite/master` branch of the mothership machine. +<4> push will stash our work away on `remotes/satellite/master` +tracking branch on the mothership machine. You could use this as +a back-up method. <5> on mothership machine, merge the work done on the satellite machine into the master branch. @@ -247,7 +251,7 @@ $ git format-patch -k -m --stdout v2.6.14..private2.6.14 | + <1> create a private branch based on a well known (but somewhat behind) tag. -<2> forward port all changes in private2.6.14 branch to master branch +<2> forward port all changes in `private2.6.14` branch to `master` branch without a formal "merging". @@ -284,13 +288,13 @@ $ mailx <3> & s 2 3 4 5 ./+to-apply & s 7 8 ./+hold-linus & q -$ git checkout master +$ git checkout -b topic/one master $ git am -3 -i -s -u ./+to-apply <4> $ compile/test $ git checkout -b hold/linus && git am -3 -i -s -u ./+hold-linus <5> $ git checkout topic/one && git rebase master <6> -$ git checkout pu && git reset --hard master <7> -$ git pull . topic/one topic/two && git pull . hold/linus <8> +$ git checkout pu && git reset --hard next <7> +$ git merge topic/one topic/two && git merge hold/linus <8> $ git checkout maint $ git cherry-pick master~4 <9> $ compile/test @@ -307,29 +311,32 @@ they are. that are not quite ready. <4> apply them, interactively, with my sign-offs. <5> create topic branch as needed and apply, again with my -sign-offs. +sign-offs. <6> rebase internal topic branch that has not been merged to the master, nor exposed as a part of a stable branch. -<7> restart "pu" every time from the master. +<7> restart `pu` every time from the next. <8> and bundle topic branches still cooking. <9> backport a critical fix. <10> create a signed tag. <11> make sure I did not accidentally rewind master beyond what I -already pushed out. "ko" shorthand points at the repository I have +already pushed out. `ko` shorthand points at the repository I have at kernel.org, and looks like this: + ------------ $ cat .git/remotes/ko URL: kernel.org:/pub/scm/git/git.git Pull: master:refs/tags/ko-master +Pull: next:refs/tags/ko-next Pull: maint:refs/tags/ko-maint Push: master +Push: next Push: +pu Push: maint ------------ + -In the output from "git show-branch", "master" should have -everything "ko-master" has. +In the output from `git show-branch`, `master` should have +everything `ko-master` has, and `next` should have +everything `ko-next` has. <12> push out the bleeding edge. <13> push the tag out, too. @@ -406,7 +413,7 @@ $ grep git /etc/shells <2> ------------ + <1> log-in shell is set to /usr/bin/git-shell, which does not -allow anything but "git push" and "git pull". The users should +allow anything but `git push` and `git pull`. The users should get an ssh access to the machine. <2> in many distributions /etc/shells needs to list what is used as the login shell. diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index 6342ea33e4..95bea66374 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -3,24 +3,45 @@ git-add(1) NAME ---- -git-add - Add files to the index file +git-add - Add file contents to the changeset to be committed next SYNOPSIS -------- -'git-add' [-n] [-v] [--] <file>... +'git-add' [-n] [-v] [-f] [--interactive] [--] <file>... DESCRIPTION ----------- -A simple wrapper for git-update-index to add files to the index, -for people used to do "cvs add". +All the changed file contents to be committed together in a single set +of changes must be "added" with the 'add' command before using the +'commit' command. This is not only for adding new files. Even modified +files must be added to the set of changes about to be committed. + +This command can be performed multiple times before a commit. The added +content corresponds to the state of specified file(s) at the time the +'add' command is used. This means the 'commit' command will not consider +subsequent changes to already added content if it is not added again before +the commit. + +The 'git status' command can be used to obtain a summary of what is included +for the next commit. + +This command can be used to add ignored files with `-f` (force) +option, but they have to be +explicitly and exactly specified from the command line. File globbing +and recursive behaviour do not add ignored files. + +Please see gitlink:git-commit[1] for alternative ways to add content to a +commit. -It only adds non-ignored files, to add ignored files use -"git update-index --add". OPTIONS ------- <file>...:: - Files to add to the index (see gitlink:git-ls-files[1]). + Files to add content from. Fileglobs (e.g. `*.c`) can + be given to add all matching files. Also a + leading directory name (e.g. `dir` to add `dir/file1` + and `dir/file2`) can be given to add all files in the + directory, recursively. -n:: Don't actually add the file(s), just show if they exist. @@ -28,33 +49,25 @@ OPTIONS -v:: Be verbose. +-f:: + Allow adding otherwise ignored files. + +\--interactive:: + Add modified contents in the working tree interactively to + the index. + \--:: This option can be used to separate command-line options from the list of files, (useful when filenames might be mistaken for command-line options). -DISCUSSION ----------- - -The list of <file> given to the command is fed to `git-ls-files` -command to list files that are not registered in the index and -are not ignored/excluded by `$GIT_DIR/info/exclude` file or -`.gitignore` file in each directory. This means two things: - -. You can put the name of a directory on the command line, and - the command will add all files in it and its subdirectories; - -. Giving the name of a file that is already in index does not - run `git-update-index` on that path. - - EXAMPLES -------- git-add Documentation/\\*.txt:: - Adds all `\*.txt` files that are not in the index under - `Documentation` directory and its subdirectories. + Adds content from all `\*.txt` files under `Documentation` + directory and its subdirectories. + Note that the asterisk `\*` is quoted from the shell in this example; this lets the command to include the files from @@ -62,15 +75,131 @@ subdirectories of `Documentation/` directory. git-add git-*.sh:: - Adds all git-*.sh scripts that are not in the index. + Considers adding content from all git-*.sh scripts. Because this example lets shell expand the asterisk (i.e. you are listing the files explicitly), it does not - add `subdir/git-foo.sh` to the index. + consider `subdir/git-foo.sh`. + +Interactive mode +---------------- +When the command enters the interactive mode, it shows the +output of the 'status' subcommand, and then goes into ints +interactive command loop. + +The command loop shows the list of subcommands available, and +gives a prompt "What now> ". In general, when the prompt ends +with a single '>', you can pick only one of the choices given +and type return, like this: + +------------ + *** Commands *** + 1: status 2: update 3: revert 4: add untracked + 5: patch 6: diff 7: quit 8: help + What now> 1 +------------ + +You also could say "s" or "sta" or "status" above as long as the +choice is unique. + +The main command loop has 6 subcommands (plus help and quit). + +status:: + + This shows the change between HEAD and index (i.e. what will be + committed if you say "git commit"), and between index and + working tree files (i.e. what you could stage further before + "git commit" using "git-add") for each path. A sample output + looks like this: ++ +------------ + staged unstaged path + 1: binary nothing foo.png + 2: +403/-35 +1/-1 git-add--interactive.perl +------------ ++ +It shows that foo.png has differences from HEAD (but that is +binary so line count cannot be shown) and there is no +difference between indexed copy and the working tree +version (if the working tree version were also different, +'binary' would have been shown in place of 'nothing'). The +other file, git-add--interactive.perl, has 403 lines added +and 35 lines deleted if you commit what is in the index, but +working tree file has further modifications (one addition and +one deletion). + +update:: + + This shows the status information and gives prompt + "Update>>". When the prompt ends with double '>>', you can + make more than one selection, concatenated with whitespace or + comma. Also you can say ranges. E.g. "2-5 7,9" to choose + 2,3,4,5,7,9 from the list. You can say '*' to choose + everything. ++ +What you chose are then highlighted with '*', +like this: ++ +------------ + staged unstaged path + 1: binary nothing foo.png +* 2: +403/-35 +1/-1 git-add--interactive.perl +------------ ++ +To remove selection, prefix the input with `-` +like this: ++ +------------ +Update>> -2 +------------ ++ +After making the selection, answer with an empty line to stage the +contents of working tree files for selected paths in the index. + +revert:: + + This has a very similar UI to 'update', and the staged + information for selected paths are reverted to that of the + HEAD version. Reverting new paths makes them untracked. + +add untracked:: + + This has a very similar UI to 'update' and + 'revert', and lets you add untracked paths to the index. + +patch:: + + This lets you choose one path out of 'status' like selection. + After choosing the path, it presents diff between the index + and the working tree file and asks you if you want to stage + the change of each hunk. You can say: + + y - add the change from that hunk to index + n - do not add the change from that hunk to index + a - add the change from that hunk and all the rest to index + d - do not the change from that hunk nor any of the rest to index + j - do not decide on this hunk now, and view the next + undecided hunk + J - do not decide on this hunk now, and view the next hunk + k - do not decide on this hunk now, and view the previous + undecided hunk + K - do not decide on this hunk now, and view the previous hunk ++ +After deciding the fate for all hunks, if there is any hunk +that was chosen, the index is updated with the selected hunks. + +diff:: + + This lets you review what will be committed (i.e. between + HEAD and index). + See Also -------- +gitlink:git-status[1] gitlink:git-rm[1] -gitlink:git-ls-files[1] +gitlink:git-mv[1] +gitlink:git-commit[1] +gitlink:git-update-index[1] Author ------ diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index 2cc32d1c5e..33b93db508 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -33,8 +33,9 @@ OPTIONS --numstat:: Similar to \--stat, but shows number of added and deleted lines in decimal notation and pathname without - abbreviation, to make it more machine friendly. Turns - off "apply". + abbreviation, to make it more machine friendly. For + binary files, outputs two `-` instead of saying + `0 0`. Turns off "apply". --summary:: Instead of applying the patch, output a condensed diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 5376760813..c464bd2fda 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -8,9 +8,10 @@ git-branch - List, create, or delete branches. SYNOPSIS -------- [verse] -'git-branch' [-r] [-a] +'git-branch' [-r | -a] [-v [--abbrev=<length>]] 'git-branch' [-l] [-f] <branchname> [<start-point>] -'git-branch' (-d | -D) <branchname>... +'git-branch' (-m | -M) [<oldbranch>] <newbranch> +'git-branch' (-d | -D) [-r] <branchname>... DESCRIPTION ----------- @@ -24,9 +25,16 @@ It will start out with a head equal to the one given as <start-point>. If no <start-point> is given, the branch will be created with a head equal to that of the currently checked out branch. +With a '-m' or '-M' option, <oldbranch> will be renamed to <newbranch>. +If <oldbranch> had a corresponding reflog, it is renamed to match +<newbranch>, and a reflog entry is created to remember the branch +renaming. If <newbranch> exists, -M must be used to force the rename +to happen. + With a `-d` or `-D` option, `<branchname>` will be deleted. You may specify more than one branch for deletion. If the branch currently -has a ref log then the ref log will also be deleted. +has a ref log then the ref log will also be deleted. Use -r together with -d +to delete remote-tracking branches. OPTIONS @@ -46,12 +54,25 @@ OPTIONS Force the creation of a new branch even if it means deleting a branch that already exists with the same name. +-m:: + Move/rename a branch and the corresponding reflog. + +-M:: + Move/rename a branch even if the new branchname already exists. + -r:: - List the remote-tracking branches. + List or delete (if used with -d) the remote-tracking branches. -a:: List both remote-tracking branches and local branches. +-v:: + Show sha1 and commit subjectline for each head. + +--abbrev=<length>:: + Alter minimum display length for sha1 in output listing, + default value is 7. + <branchname>:: The name of the branch to create or delete. The new branch name must pass all checks defined by @@ -63,6 +84,12 @@ OPTIONS be given as a branch name, a commit-id, or a tag. If this option is omitted, the current branch is assumed. +<oldbranch>:: + The name of an existing branch to rename. + +<newbranch>:: + The new name for an existing branch. The same restrictions as for + <branchname> applies. Examples @@ -85,10 +112,12 @@ Delete unneeded branch:: ------------ $ git clone git://git.kernel.org/.../git.git my.git $ cd my.git -$ git branch -D todo <1> +$ git branch -d -r todo html man <1> +$ git branch -D test <2> ------------ + -<1> delete todo branch even if the "master" branch does not have all +<1> delete remote-tracking branches "todo", "html", "man" +<2> delete "test" branch even if the "master" branch does not have all commits from todo branch. diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index 4cb42237b5..874934a332 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -11,27 +11,25 @@ SYNOPSIS [verse] 'git-clone' [--template=<template_directory>] [-l [-s]] [-q] [-n] [--bare] [-o <name>] [-u <upload-pack>] [--reference <repository>] - [--use-separate-remote | --use-immingled-remote] <repository> - [<directory>] + <repository> [<directory>] DESCRIPTION ----------- -Clones a repository into a newly created directory. All remote -branch heads are copied under `$GIT_DIR/refs/heads/`, except -that the remote `master` is also copied to `origin` branch. -In addition, `$GIT_DIR/remotes/origin` file is set up to have -this line: +Clones a repository into a newly created directory, creates +remote-tracking branches for each branch in the cloned repository +(visible using `git branch -r`), and creates and checks out a master +branch equal to the cloned repository's master branch. - Pull: master:origin - -This is to help the typical workflow of working off of the -remote `master` branch. Every time `git pull` without argument -is run, the progress on the remote `master` branch is tracked by -copying it into the local `origin` branch, and merged into the -branch you are currently working on. Remote branches other than -`master` are also added there to be tracked. +After the clone, a plain `git fetch` without arguments will update +all the remote-tracking branches, and a `git pull` without +arguments will in addition merge the remote master branch into the +current branch. +This default configuration is achieved by creating references to +the remote branch heads under `$GIT_DIR/refs/remotes/origin` and +by initializing `remote.origin.url` and `remote.origin.fetch` +configuration variables. OPTIONS ------- @@ -100,18 +98,6 @@ OPTIONS if unset the templates are taken from the installation defined default, typically `/usr/share/git-core/templates`. ---use-separate-remote:: - Save remotes heads under `$GIT_DIR/remotes/origin/` instead - of `$GIT_DIR/refs/heads/`. Only the local master branch is - saved in the latter. This is the default. - ---use-immingled-remote:: - Save remotes heads in the same namespace as the local - heads, `$GIT_DIR/refs/heads/'. In regular repositories, - this is a legacy setup git-clone created by default in - older Git versions, and will be removed before the next - major release. - <repository>:: The (possibly remote) repository to clone from. It can be any URL git-fetch supports. diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 517a86b238..0b74cd708e 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -14,25 +14,41 @@ SYNOPSIS DESCRIPTION ----------- -Updates the index file for given paths, or all modified files if -'-a' is specified, and makes a commit object. The command specified -by either the VISUAL or EDITOR environment variables are used to edit -the commit log message. +Use 'git commit' when you want to record your changes into the repository +along with a log message describing what the commit is about. All changes +to be committed must be explicitly identified using one of the following +methods: -Several environment variable are used during commits. They are -documented in gitlink:git-commit-tree[1]. +1. by using gitlink:git-add[1] to incrementally "add" changes to the + next commit before using the 'commit' command (Note: even modified + files must be "added"); +2. by using gitlink:git-rm[1] to identify content removal for the next + commit, again before using the 'commit' command; + +3. by directly listing files containing changes to be committed as arguments + to the 'commit' command, in which cases only those files alone will be + considered for the commit; + +4. by using the -a switch with the 'commit' command to automatically "add" + changes from all known files i.e. files that have already been committed + before, and perform the actual commit. + +The gitlink:git-status[1] command can be used to obtain a +summary of what is included by any of the above for the next +commit by giving the same set of parameters you would give to +this command. + +If you make a commit and then found a mistake immediately after +that, you can recover from it with gitlink:git-reset[1]. -This command can run `commit-msg`, `pre-commit`, and -`post-commit` hooks. See link:hooks.html[hooks] for more -information. OPTIONS ------- -a|--all:: - Update all paths in the index file. This flag notices - files that have been modified and deleted, but new files - you have not told git about are not affected. + Tell the command to automatically stage files that have + been modified and deleted, but new files you have not + told git about are not affected. -c or -C <commit>:: Take existing commit object, and reuse the log message @@ -55,16 +71,13 @@ OPTIONS -s|--signoff:: Add Signed-off-by line at the end of the commit message. --v|--verify:: - Look for suspicious lines the commit introduces, and - abort committing if there is one. The definition of - 'suspicious lines' is currently the lines that has - trailing whitespaces, and the lines whose indentation - has a SP character immediately followed by a TAB - character. This is the default. - --n|--no-verify:: - The opposite of `--verify`. +--no-verify:: + By default, the command looks for suspicious lines the + commit introduces, and aborts committing if there is one. + The definition of 'suspicious lines' is currently the + lines that has trailing whitespaces, and the lines whose + indentation has a SP character immediately followed by a + TAB character. This option turns off the check. -e|--edit:: The message taken from file with `-F`, command line with @@ -95,69 +108,140 @@ but can be used to amend a merge commit. -- -i|--include:: - Instead of committing only the files specified on the - command line, update them in the index file and then - commit the whole index. This is the traditional - behavior. + Before making a commit out of staged contents so far, + stage the contents of paths given on the command line + as well. This is usually not what you want unless you + are concluding a conflicted merge. --o|--only:: - Commit only the files specified on the command line. - This format cannot be used during a merge, nor when the - index and the latest commit does not match on the - specified paths to avoid confusion. +-q|--quiet:: + Supress commit summary message. \--:: Do not interpret any more arguments as options. <file>...:: - Files to be committed. The meaning of these is - different between `--include` and `--only`. Without - either, it defaults `--only` semantics. - -If you make a commit and then found a mistake immediately after -that, you can recover from it with gitlink:git-reset[1]. + When files are given on the command line, the command + commits the contents of the named files, without + recording the changes already staged. The contents of + these files are also staged for the next commit on top + of what have been staged before. -Discussion ----------- - -`git commit` without _any_ parameter commits the tree structure -recorded by the current index file. This is a whole-tree commit -even the command is invoked from a subdirectory. - -`git commit --include paths...` is equivalent to - - git update-index --remove paths... - git commit - -That is, update the specified paths to the index and then commit -the whole tree. - -`git commit paths...` largely bypasses the index file and -commits only the changes made to the specified paths. It has -however several safety valves to prevent confusion. - -. It refuses to run during a merge (i.e. when - `$GIT_DIR/MERGE_HEAD` exists), and reminds trained git users - that the traditional semantics now needs -i flag. - -. It refuses to run if named `paths...` are different in HEAD - and the index (ditto about reminding). Added paths are OK. - This is because an earlier `git diff` (not `git diff HEAD`) - would have shown the differences since the last `git - update-index paths...` to the user, and an inexperienced user - may mistakenly think that the changes between the index and - the HEAD (i.e. earlier changes made before the last `git - update-index paths...` was done) are not being committed. - -. It reads HEAD commit into a temporary index file, updates the - specified `paths...` and makes a commit. At the same time, - the real index file is also updated with the same `paths...`. +EXAMPLES +-------- +When recording your own work, the contents of modified files in +your working tree are temporarily stored to a staging area +called the "index" with gitlink:git-add[1]. Removal +of a file is staged with gitlink:git-rm[1]. After building the +state to be committed incrementally with these commands, `git +commit` (without any pathname parameter) is used to record what +has been staged so far. This is the most basic form of the +command. An example: + +------------ +$ edit hello.c +$ git rm goodbye.c +$ git add hello.c +$ git commit +------------ + +//////////// +We should fix 'git rm' to remove goodbye.c from both index and +working tree for the above example. +//////////// + +Instead of staging files after each individual change, you can +tell `git commit` to notice the changes to the files whose +contents are tracked in +your working tree and do corresponding `git add` and `git rm` +for you. That is, this example does the same as the earlier +example if there is no other change in your working tree: + +------------ +$ edit hello.c +$ rm goodbye.c +$ git commit -a +------------ + +The command `git commit -a` first looks at your working tree, +notices that you have modified hello.c and removed goodbye.c, +and performs necessary `git add` and `git rm` for you. + +After staging changes to many files, you can alter the order the +changes are recorded in, by giving pathnames to `git commit`. +When pathnames are given, the command makes a commit that +only records the changes made to the named paths: + +------------ +$ edit hello.c hello.h +$ git add hello.c hello.h +$ edit Makefile +$ git commit Makefile +------------ + +This makes a commit that records the modification to `Makefile`. +The changes staged for `hello.c` and `hello.h` are not included +in the resulting commit. However, their changes are not lost -- +they are still staged and merely held back. After the above +sequence, if you do: + +------------ +$ git commit +------------ + +this second commit would record the changes to `hello.c` and +`hello.h` as expected. + +After a merge (initiated by either gitlink:git-merge[1] or +gitlink:git-pull[1]) stops because of conflicts, cleanly merged +paths are already staged to be committed for you, and paths that +conflicted are left in unmerged state. You would have to first +check which paths are conflicting with gitlink:git-status[1] +and after fixing them manually in your working tree, you would +stage the result as usual with gitlink:git-add[1]: + +------------ +$ git status | grep unmerged +unmerged: hello.c +$ edit hello.c +$ git add hello.c +------------ + +After resolving conflicts and staging the result, `git ls-files -u` +would stop mentioning the conflicted path. When you are done, +run `git commit` to finally record the merge: + +------------ +$ git commit +------------ + +As with the case to record your own changes, you can use `-a` +option to save typing. One difference is that during a merge +resolution, you cannot use `git commit` with pathnames to +alter the order the changes are committed, because the merge +should be recorded as a single commit. In fact, the command +refuses to run when given pathnames (but see `-i` option). + + +ENVIRONMENT VARIABLES +--------------------- +The command specified by either the VISUAL or EDITOR environment +variables is used to edit the commit log message. + +HOOKS +----- +This command can run `commit-msg`, `pre-commit`, and +`post-commit` hooks. See link:hooks.html[hooks] for more +information. -`git commit --all` updates the index file with _all_ changes to -the working tree, and makes a whole-tree commit, regardless of -which subdirectory the command is invoked in. +SEE ALSO +-------- +gitlink:git-add[1], +gitlink:git-rm[1], +gitlink:git-mv[1], +gitlink:git-merge[1], +gitlink:git-commit-tree[1] Author ------ diff --git a/Documentation/git-count-objects.txt b/Documentation/git-count-objects.txt index 198ce77a8a..c59df6438c 100644 --- a/Documentation/git-count-objects.txt +++ b/Documentation/git-count-objects.txt @@ -20,8 +20,8 @@ OPTIONS -v:: In addition to the number of loose objects and disk space consumed, it reports the number of in-pack - objects, and number of objects that can be removed by - running `git-prune-packed`. + objects, number of packs, and number of objects that can be + removed by running `git-prune-packed`. Author diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt index 228c4d95bd..8977877b21 100644 --- a/Documentation/git-diff.txt +++ b/Documentation/git-diff.txt @@ -8,36 +8,54 @@ git-diff - Show changes between commits, commit and working tree, etc SYNOPSIS -------- -'git-diff' [ --diff-options ] <tree-ish>{0,2} [<path>...] +'git-diff' [ --diff-options ] <commit>{0,2} [--] [<path>...] DESCRIPTION ----------- Show changes between two trees, a tree and the working tree, a tree and the index file, or the index file and the working tree. -The combination of what is compared with what is determined by -the number of trees given to the command. -* When no <tree-ish> is given, the working tree and the index - file are compared, using `git-diff-files`. +'git-diff' [--options] [--] [<path>...]:: -* When one <tree-ish> is given, the working tree and the named - tree are compared, using `git-diff-index`. The option - `--cached` can be given to compare the index file and - the named tree. + This form is to view the changes you made relative to + the index (staging area for the next commit). In other + words, the differences are what you _could_ tell git to + further add to the index but you still haven't. You can + stage these changes by using gitlink:git-add[1]. + +'git-diff' [--options] --cached [<commit>] [--] [<path>...]:: + + This form is to view the changes you staged for the next + commit relative to the named <commit>. Typically you + would want comparison with the latest commit, so if you + do not give <commit>, it defaults to HEAD. + +'git-diff' [--options] <commit> [--] [<path>...]:: + + This form is to view the changes you have in your + working tree relative to the named <commit>. You can + use HEAD to compare it with the latest commit, or a + branch name to compare with the tip of a different + branch. + +'git-diff' [--options] <commit> <commit> [--] [<path>...]:: + + This form is to view the changes between two <commit>, + for example, tips of two branches. + +Just in case if you are doing something exotic, it should be +noted that all of the <commit> in the above description can be +any <tree-ish>. -* When two <tree-ish>s are given, these two trees are compared - using `git-diff-tree`. OPTIONS ------- ---diff-options:: - '--diff-options' are passed to the `git-diff-files`, - `git-diff-index`, and `git-diff-tree` commands. See the - documentation for these commands for description. +include::diff-options.txt[] <path>...:: - The <path> arguments are also passed to `git-diff-\*` - commands. + The <paths> parameters, when given, are used to limit + the diff to the named paths (you can give directory + names and get diff for all files under them). EXAMPLES @@ -51,7 +69,7 @@ $ git diff --cached <2> $ git diff HEAD <3> ------------ + -<1> changes in the working tree since your last git-update-index. +<1> changes in the working tree not yet staged for the next commit. <2> changes between the index and your last commit; what you would be committing if you run "git commit" without "-a" option. <3> changes in the working tree since your last commit; what you diff --git a/Documentation/git-merge-file.txt b/Documentation/git-merge-file.txt new file mode 100644 index 0000000000..29d3faa556 --- /dev/null +++ b/Documentation/git-merge-file.txt @@ -0,0 +1,92 @@ +git-merge-file(1) +================= + +NAME +---- +git-merge-file - three-way file merge + + +SYNOPSIS +-------- +[verse] +'git-merge-file' [-L <current-name> [-L <base-name> [-L <other-name>]]] + [-p|--stdout] [-q|--quiet] <current-file> <base-file> <other-file> + + +DESCRIPTION +----------- +git-file-merge incorporates all changes that lead from the `<base-file>` +to `<other-file>` into `<current-file>`. The result ordinarily goes into +`<current-file>`. git-merge-file is useful for combining separate changes +to an original. Suppose `<base-file>` is the original, and both +`<current-file>` and `<other-file>` are modifications of `<base-file>`. +Then git-merge-file combines both changes. + +A conflict occurs if both `<current-file>` and `<other-file>` have changes +in a common segment of lines. If a conflict is found, git-merge-file +normally outputs a warning and brackets the conflict with <<<<<<< and +>>>>>>> lines. A typical conflict will look like this: + + <<<<<<< A + lines in file A + ======= + lines in file B + >>>>>>> B + +If there are conflicts, the user should edit the result and delete one of +the alternatives. + +The exit value of this program is negative on error, and the number of +conflicts otherwise. If the merge was clean, the exit value is 0. + +git-merge-file is designed to be a minimal clone of RCS merge, that is, it +implements all of RCS merge's functionality which is needed by +gitlink:git[1]. + + +OPTIONS +------- + +-L <label>:: + This option may be given up to three times, and + specifies labels to be used in place of the + corresponding file names in conflict reports. That is, + `git-merge-file -L x -L y -L z a b c` generates output that + looks like it came from files x, y and z instead of + from files a, b and c. + +-p:: + Send results to standard output instead of overwriting + `<current-file>`. + +-q:: + Quiet; do not warn about conflicts. + + +EXAMPLES +-------- + +git merge-file README.my README README.upstream:: + + combines the changes of README.my and README.upstream since README, + tries to merge them and writes the result into README.my. + +git merge-file -L a -L b -L c tmp/a123 tmp/b234 tmp/c345:: + + merges tmp/a123 and tmp/c345 with the base tmp/b234, but uses labels + `a` and `c` instead of `tmp/a123` and `tmp/c345`. + + +Author +------ +Written by Johannes Schindelin <johannes.schindelin@gmx.de> + + +Documentation +-------------- +Documentation by Johannes Schindelin and the git-list <git@vger.kernel.org>, +with parts copied from the original documentation of RCS merge. + +GIT +--- +Part of the gitlink:git[7] suite diff --git a/Documentation/git-merge-index.txt b/Documentation/git-merge-index.txt index 6cd0601082..0cf505ea84 100644 --- a/Documentation/git-merge-index.txt +++ b/Documentation/git-merge-index.txt @@ -40,8 +40,8 @@ If "git-merge-index" is called with multiple <file>s (or -a) then it processes them in turn only stopping if merge returns a non-zero exit code. -Typically this is run with the a script calling the merge command from -the RCS package. +Typically this is run with the a script calling git's imitation of +the merge command from the RCS package. A sample script called "git-merge-one-file" is included in the distribution. diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index bebf30ad3d..e2954aa76e 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -8,12 +8,14 @@ git-merge - Grand Unified Merge Driver SYNOPSIS -------- -'git-merge' [-n] [--no-commit] [-s <strategy>]... <msg> <head> <remote> <remote>... - +[verse] +'git-merge' [-n] [--no-commit] [--squash] [-s <strategy>]... + [--reflog-action=<action>] + -m=<msg> <remote> <remote>... DESCRIPTION ----------- -This is the top-level user interface to the merge machinery +This is the top-level interface to the merge machinery which drives multiple merge strategy scripts. @@ -27,13 +29,19 @@ include::merge-options.txt[] to give a good default for automated `git-merge` invocations. <head>:: - our branch head commit. + Our branch head commit. This has to be `HEAD`, so new + syntax does not require it <remote>:: - other branch head merged into our branch. You need at + Other branch head merged into our branch. You need at least one <remote>. Specifying more than one <remote> obviously means you are trying an Octopus. +--reflog-action=<action>:: + This is used internally when `git-pull` calls this command + to record that the merge was created by `pull` command + in the `ref-log` entry that results from the merge. + include::merge-strategies.txt[] diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index d4ae99fa53..197f4b512f 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -49,12 +49,14 @@ corresponding remotes file---see below), then all the refs that exist both on the local side and on the remote side are updated. + -Some short-cut notations are also supported. +`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`. + -* `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`. -* A parameter <ref> without a colon is equivalent to - <ref>`:`<ref>, hence updates <ref> in the destination from <ref> - in the source. +A parameter <ref> without a colon is equivalent to +<ref>`:`<ref>, hence updates <ref> in the destination from <ref> +in the source. ++ +Pushing an empty <src> allows you to delete the <dst> ref from +the remote repository. \--all:: Instead of naming each ref to push, specifies that all @@ -75,7 +77,8 @@ include::urls.txt[] Author ------ -Written by Junio C Hamano <junkio@cox.net> +Written by Junio C Hamano <junkio@cox.net>, later rewritten in C +by Linus Torvalds <torvalds@osdl.org> Documentation -------------- diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 11bd9c0adc..0ff2890c7f 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -8,7 +8,7 @@ git-read-tree - Reads tree information into the index SYNOPSIS -------- -'git-read-tree' (<tree-ish> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]]) +'git-read-tree' (<tree-ish> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] <tree-ish1> [<tree-ish2> [<tree-ish3>]]) DESCRIPTION @@ -71,6 +71,20 @@ OPTIONS directory. Note that the `<prefix>/` value must end with a slash. +--exclude-per-directory=<gitignore>:: + When running the command with `-u` and `-m` options, the + merge result may need to overwrite paths that are not + tracked in the current branch. The command usually + refuses to proceed with the merge to avoid losing such a + path. However this safety valve sometimes gets in the + way. For example, it often happens that the other + branch added a file that used to be a generated file in + your branch, and the safety valve triggers when you try + to switch to that branch after you ran `make` but before + running `make clean` to remove the generated file. This + option tells the command to read per-directory exclude + file (usually '.gitignore') and allows such an untracked + but explicitly ignored file to be overwritten. <tree-ish#>:: The id of the tree object(s) to be read/merged. diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt index 8199615dde..b379ec5075 100644 --- a/Documentation/git-repo-config.txt +++ b/Documentation/git-repo-config.txt @@ -10,6 +10,7 @@ SYNOPSIS -------- [verse] 'git-repo-config' [--global] [type] name [value [value_regex]] +'git-repo-config' [--global] [type] --add name value 'git-repo-config' [--global] [type] --replace-all name [value [value_regex]] 'git-repo-config' [--global] [type] --get name [value_regex] 'git-repo-config' [--global] [type] --get-all name [value_regex] @@ -23,7 +24,8 @@ You can query/set/replace/unset options with this command. The name is actually the section and the key separated by a dot, and the value will be escaped. -If you want to set/unset an option which can occur on multiple +Multiple lines can be added to an option by using the '--add' option. +If you want to update or unset an option which can occur on multiple lines, a POSIX regexp `value_regex` needs to be given. Only the existing values that match the regexp are updated or unset. If you want to handle the lines that do *not* match the regex, just @@ -53,6 +55,10 @@ OPTIONS Default behavior is to replace at most one line. This replaces all lines matching the key (and optionally the value_regex). +--add:: + Adds a new line to the option without altering any existing + values. This is the same as providing '^$' as the value_regex. + --get:: Get the value for a given key (optionally filtered by a regex matching the value). Returns error code 1 if the key was not @@ -77,6 +83,12 @@ OPTIONS -l, --list:: List all variables set in config file. +--bool:: + git-repo-config will ensure that the output is "true" or "false" + +--int:: + git-repo-config will ensure that the output is a simple decimal number + ENVIRONMENT ----------- @@ -188,6 +200,12 @@ To actually match only values with an exclamation mark, you have to % git repo-config section.key value '[!]' ------------ +To add a new proxy, without altering any of the existing ones, use + +------------ +% git repo-config core.gitproxy '"proxy" for example.com' +------------ + include::config.txt[] diff --git a/Documentation/git-rerere.txt b/Documentation/git-rerere.txt index 8b6b651237..116dca4c06 100644 --- a/Documentation/git-rerere.txt +++ b/Documentation/git-rerere.txt @@ -7,8 +7,7 @@ git-rerere - Reuse recorded resolve SYNOPSIS -------- -'git-rerere' - +'git-rerere' [clear|diff|status] DESCRIPTION ----------- @@ -27,6 +26,38 @@ results and applying the previously recorded hand resolution. You need to create `$GIT_DIR/rr-cache` directory to enable this command. + +COMMANDS +-------- + +Normally, git-rerere is run without arguments or user-intervention. +However, it has several commands that allow it to interact with +its working state. + +'clear':: + +This resets the metadata used by rerere if a merge resolution is to be +is aborted. Calling gitlink:git-am[1] --skip or gitlink:git-rebase[1] +[--skip|--abort] will automatcally invoke this command. + +'diff':: + +This displays diffs for the current state of the resolution. It is +useful for tracking what has changed while the user is resolving +conflicts. Additional arguments are passed directly to the system +diff(1) command installed in PATH. + +'status':: + +Like diff, but this only prints the filenames that will be tracked +for resolutions. + +'gc':: + +This command is used to prune records of conflicted merge that +occurred long time ago. + + DISCUSSION ---------- diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index 73a0ffc410..4f424782eb 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -7,7 +7,9 @@ git-reset - Reset current HEAD to the specified state SYNOPSIS -------- -'git-reset' [--mixed | --soft | --hard] [<commit-ish>] +[verse] +'git-reset' [--mixed | --soft | --hard] [<commit>] +'git-reset' [--mixed] <commit> [--] <paths>... DESCRIPTION ----------- @@ -21,6 +23,10 @@ the undo in the history. If you want to undo a commit other than the latest on a branch, gitlink:git-revert[1] is your friend. +The second form with 'paths' is used to revert selected paths in +the index from a given commit, without moving HEAD. + + OPTIONS ------- --mixed:: @@ -31,15 +37,15 @@ OPTIONS --soft:: Does not touch the index file nor the working tree at all, but requires them to be in a good order. This leaves all your changed - files "Updated but not checked in", as gitlink:git-status[1] would + files "Added but not yet committed", as gitlink:git-status[1] would put it. --hard:: Matches the working tree and index to that of the tree being switched to. Any changes to tracked files in the working tree - since <commit-ish> are lost. + since <commit> are lost. -<commit-ish>:: +<commit>:: Commit to make the current HEAD. Examples diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index ec43c0b3a8..9e0dcf8d3f 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -10,6 +10,7 @@ SYNOPSIS -------- [verse] 'git-rev-list' [ \--max-count=number ] + [ \--skip=number ] [ \--max-age=timestamp ] [ \--min-age=timestamp ] [ \--sparse ] @@ -139,6 +140,10 @@ limiting may be applied. Limit the number of commits output. +--skip='number':: + + Skip 'number' commits before starting to show the commit output. + --since='date', --after='date':: Show commits more recent than a specific date. diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt index 66fc478f57..3a8f279e1a 100644 --- a/Documentation/git-rm.txt +++ b/Documentation/git-rm.txt @@ -7,51 +7,54 @@ git-rm - Remove files from the working tree and from the index SYNOPSIS -------- -'git-rm' [-f] [-n] [-v] [--] <file>... +'git-rm' [-f] [-n] [-r] [--cached] [--] <file>... DESCRIPTION ----------- -A convenience wrapper for git-update-index --remove. For those coming -from cvs, git-rm provides an operation similar to "cvs rm" or "cvs -remove". +Remove files from the working tree and from the index. The +files have to be identical to the tip of the branch, and no +updates to its contents must have been placed in the staging +area (aka index). OPTIONS ------- <file>...:: - Files to remove from the index and optionally, from the - working tree as well. + Files to remove. Fileglobs (e.g. `*.c`) can be given to + remove all matching files. Also a leading directory name + (e.g. `dir` to add `dir/file1` and `dir/file2`) can be + given to remove all files in the directory, recursively, + but this requires `-r` option to be given for safety. -f:: - Remove files from the working tree as well as from the index. + Override the up-to-date check. -n:: Don't actually remove the file(s), just show if they exist in the index. --v:: - Be verbose. +-r:: + Allow recursive removal when a leading directory name is + given. \--:: This option can be used to separate command-line options from the list of files, (useful when filenames might be mistaken for command-line options). +\--cached:: + This option can be used to tell the command to remove + the paths only from the index, leaving working tree + files. + DISCUSSION ---------- -The list of <file> given to the command is fed to `git-ls-files` -command to list files that are registered in the index and -are not ignored/excluded by `$GIT_DIR/info/exclude` file or -`.gitignore` file in each directory. This means two things: - -. You can put the name of a directory on the command line, and the - command will remove all files in it and its subdirectories (the - directories themselves are never removed from the working tree); - -. Giving the name of a file that is not in the index does not - remove that file. +The list of <file> given to the command can be exact pathnames, +file glob patterns, or leading directory name. The command +removes only the paths that is known to git. Giving the name of +a file that you have not told git about does not remove that file. EXAMPLES @@ -69,10 +72,10 @@ subdirectories of `Documentation/` directory. git-rm -f git-*.sh:: Remove all git-*.sh scripts that are in the index. The files - are removed from the index, and (because of the -f option), - from the working tree as well. Because this example lets the - shell expand the asterisk (i.e. you are listing the files - explicitly), it does not remove `subdir/git-foo.sh`. + are removed from the index, and from the working + tree. Because this example lets the shell expand the + asterisk (i.e. you are listing the files explicitly), it + does not remove `subdir/git-foo.sh`. See Also -------- diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt index d54fc3e5c6..95fa9010c1 100644 --- a/Documentation/git-shortlog.txt +++ b/Documentation/git-shortlog.txt @@ -8,6 +8,7 @@ git-shortlog - Summarize 'git log' output SYNOPSIS -------- git-log --pretty=short | 'git-shortlog' [-h] [-n] [-s] +git-shortlog [-n|--number] [-s|--summary] [<committish>...] DESCRIPTION ----------- diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt index a2445a48fc..912e15bcba 100644 --- a/Documentation/git-show-branch.txt +++ b/Documentation/git-show-branch.txt @@ -8,9 +8,10 @@ git-show-branch - Show branches and their commits SYNOPSIS -------- [verse] -'git-show-branch' [--all] [--heads] [--tags] [--topo-order] [--current] +'git-show-branch' [--all] [--remotes] [--topo-order] [--current] [--more=<n> | --list | --independent | --merge-base] - [--no-name | --sha1-name] [<rev> | <glob>]... + [--no-name | --sha1-name] [--topics] [<rev> | <glob>]... +'git-show-branch' --reflog[=<n>] <ref> DESCRIPTION ----------- @@ -37,9 +38,11 @@ OPTIONS branches under $GIT_DIR/refs/heads/topic, giving `topic/*` would show all of them. ---all --heads --tags:: - Show all refs under $GIT_DIR/refs, $GIT_DIR/refs/heads, - and $GIT_DIR/refs/tags, respectively. +-r|--remotes:: + Show the remote-tracking branches. + +-a|--all:: + Show both remote-tracking branches and local branches. --current:: With this option, the command includes the current @@ -86,6 +89,18 @@ OPTIONS of "master"), name them with the unique prefix of their object names. +--topics:: + Shows only commits that are NOT on the first branch given. + This helps track topic branches by hiding any commit that + is already in the main line of development. When given + "git show-branch --topics master topic1 topic2", this + will show the revisions given by "git rev-list {caret}master + topic1 topic2" + +--reflog[=<n>] <ref>:: + Shows <n> most recent ref-log entries for the given ref. + + Note that --more, --list, --independent and --merge-base options are mutually exclusive. diff --git a/Documentation/git-show.txt b/Documentation/git-show.txt index 4c880a8717..98dea6125d 100644 --- a/Documentation/git-show.txt +++ b/Documentation/git-show.txt @@ -3,20 +3,27 @@ git-show(1) NAME ---- -git-show - Show one commit with difference it introduces +git-show - Show various types of objects SYNOPSIS -------- -'git-show' <option>... +'git-show' [options] <object>... DESCRIPTION ----------- -Shows commit log and textual diff for a single commit. The -command internally invokes 'git-rev-list' piped to -'git-diff-tree', and takes command line options for both of -these commands. It also presents the merge commit in a special -format as produced by 'git-diff-tree --cc'. +Shows one or more objects (blobs, trees, tags and commits). + +For commits it shows the log message and textual diff. It also +presents the merge commit in a special format as produced by +'git-diff-tree --cc'. + +For tags, it shows the tag message and the referenced objects. + +For trees, it shows the names (equivalent to gitlink:git-ls-tree[1] +with \--name-only). + +For plain blobs, it shows the plain contents. This manual page describes only the most frequently used options. @@ -28,6 +35,25 @@ OPTIONS include::pretty-formats.txt[] + +EXAMPLES +-------- + +git show v1.0.0:: + Shows the tag `v1.0.0`. + +git show v1.0.0^{tree}:: + Shows the tree pointed to by the tag `v1.0.0`. + +git show next~10:Documentation/README + Shows the contents of the file `Documentation/README` as + they were current in the 10th last commit of the branch + `next`. + +git show master:Makefile master:t/Makefile + Concatenates the contents of said Makefiles in the head + of the branch `master`. + Author ------ Written by Linus Torvalds <torvalds@osdl.org> and diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index a764d1f8ee..f5f57e8f87 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -49,7 +49,7 @@ latest revision. Note: You should never attempt to modify the remotes/git-svn branch outside of git-svn. Instead, create a branch from -remotes/git-svn and work on that branch. Use the 'commit' +remotes/git-svn and work on that branch. Use the 'dcommit' command (see below) to write git commits back to remotes/git-svn. @@ -57,12 +57,14 @@ See '<<fetch-args,Additional Fetch Arguments>>' if you are interested in manually joining branches on commit. 'dcommit':: - Commit all diffs from the current HEAD directly to the SVN + Commit all diffs from a specified head directly to the SVN repository, and then rebase or reset (depending on whether or - not there is a diff between SVN and HEAD). It is recommended + not there is a diff between SVN and head). It is recommended that you run git-svn fetch and rebase (not pull) your commits against the latest changes in the SVN repository. - This is advantageous over 'commit' (below) because it produces + An optional command-line argument may be specified as an + alternative to HEAD. + This is advantageous over 'set-tree' (below) because it produces cleaner, more linear history. 'log':: @@ -87,7 +89,7 @@ manually joining branches on commit. Any other arguments are passed directly to `git log' -'commit':: +'set-tree':: You should consider using 'dcommit' instead of this command. Commit specified commit or tree objects to SVN. This relies on your imported fetch data being up-to-date. This makes @@ -170,7 +172,7 @@ This can allow you to make partial mirrors when running fetch. -:: --stdin:: -Only used with the 'commit' command. +Only used with the 'set-tree' command. Read a list of commits from stdin and commit them in reverse order. Only the leading sha1 is read from each line, so @@ -178,7 +180,7 @@ git-rev-list --pretty=oneline output can be used. --rmdir:: -Only used with the 'dcommit', 'commit' and 'commit-diff' commands. +Only used with the 'dcommit', 'set-tree' and 'commit-diff' commands. Remove directories from the SVN tree if there are no files left behind. SVN can version empty directories, and they are not @@ -191,7 +193,7 @@ repo-config key: svn.rmdir -e:: --edit:: -Only used with the 'dcommit', 'commit' and 'commit-diff' commands. +Only used with the 'dcommit', 'set-tree' and 'commit-diff' commands. Edit the commit message before committing to SVN. This is off by default for objects that are commits, and forced on when committing @@ -202,7 +204,7 @@ repo-config key: svn.edit -l<num>:: --find-copies-harder:: -Only used with the 'dcommit', 'commit' and 'commit-diff' commands. +Only used with the 'dcommit', 'set-tree' and 'commit-diff' commands. They are both passed directly to git-diff-tree see gitlink:git-diff-tree[1] for more information. @@ -274,7 +276,7 @@ ADVANCED OPTIONS -b<refname>:: --branch <refname>:: -Used with 'fetch' or 'commit'. +Used with 'fetch', 'dcommit' or 'set-tree'. This can be used to join arbitrary git branches to remotes/git-svn on new commits where the tree object is equivalent. @@ -368,7 +370,7 @@ SVN was very wrong. Basic Examples ~~~~~~~~~~~~~~ -Tracking and contributing to an Subversion managed-project: +Tracking and contributing to a Subversion-managed project: ------------------------------------------------------------------------ # Initialize a repo (like git init-db): @@ -377,10 +379,9 @@ Tracking and contributing to an Subversion managed-project: git-svn fetch # Create your own branch to hack on: git checkout -b my-branch remotes/git-svn -# Commit only the git commits you want to SVN: - git-svn commit <tree-ish> [<tree-ish_2> ...] -# Commit all the git commits from my-branch that don't exist in SVN: - git-svn commit remotes/git-svn..my-branch +# Do some work, and then commit your new changes to SVN, as well as +# automatically updating your working HEAD: + git-svn dcommit # Something is committed to SVN, rebase the latest into your branch: git-svn fetch && git rebase remotes/git-svn # Append svn:ignore settings to the default git exclude file: @@ -391,11 +392,11 @@ REBASE VS. PULL --------------- Originally, git-svn recommended that the remotes/git-svn branch be -pulled from. This is because the author favored 'git-svn commit B' -to commit a single head rather than the 'git-svn commit A..B' notation +pulled from. This is because the author favored 'git-svn set-tree B' +to commit a single head rather than the 'git-svn set-tree A..B' notation to commit multiple commits. -If you use 'git-svn commit A..B' to commit several diffs and you do not +If you use 'git-svn set-tree A..B' to commit several diffs and you do not have the latest remotes/git-svn merged into my-branch, you should use 'git rebase' to update your work branch instead of 'git pull'. 'pull' can cause non-linear history to be flattened when committing into SVN, @@ -404,26 +405,24 @@ which can lead to merge commits reversing previous commits in SVN. DESIGN PHILOSOPHY ----------------- Merge tracking in Subversion is lacking and doing branched development -with Subversion is cumbersome as a result. git-svn completely forgoes -any automated merge/branch tracking on the Subversion side and leaves it -entirely up to the user on the git side. It's simply not worth it to do -a useful translation when the original signal is weak. +with Subversion is cumbersome as a result. git-svn does not do +automated merge/branch tracking by default and leaves it entirely up to +the user on the git side. [[tracking-multiple-repos]] TRACKING MULTIPLE REPOSITORIES OR BRANCHES ------------------------------------------ -This is for advanced users, most users should ignore this section. - Because git-svn does not care about relationships between different branches or directories in a Subversion repository, git-svn has a simple hack to allow it to track an arbitrary number of related _or_ unrelated -SVN repositories via one git repository. Simply set the GIT_SVN_ID -environment variable to a name other other than "git-svn" (the default) -and git-svn will ignore the contents of the $GIT_DIR/svn/git-svn directory -and instead do all of its work in $GIT_DIR/svn/$GIT_SVN_ID for that -invocation. The interface branch will be remotes/$GIT_SVN_ID, instead of -remotes/git-svn. Any remotes/$GIT_SVN_ID branch should never be modified -by the user outside of git-svn commands. +SVN repositories via one git repository. Simply use the --id/-i flag or +set the GIT_SVN_ID environment variable to a name other other than +"git-svn" (the default) and git-svn will ignore the contents of the +$GIT_DIR/svn/git-svn directory and instead do all of its work in +$GIT_DIR/svn/$GIT_SVN_ID for that invocation. The interface branch will +be remotes/$GIT_SVN_ID, instead of remotes/git-svn. Any +remotes/$GIT_SVN_ID branch should never be modified by the user outside +of git-svn commands. [[fetch-args]] ADDITIONAL FETCH ARGUMENTS @@ -486,7 +485,8 @@ If you are not using the SVN::* Perl libraries and somebody commits a conflicting changeset to SVN at a bad moment (right before you commit) causing a conflict and your commit to fail, your svn working tree ($GIT_DIR/git-svn/tree) may be dirtied. The easiest thing to do is -probably just to rm -rf $GIT_DIR/git-svn/tree and run 'rebuild'. +probably just to rm -rf $GIT_DIR/git-svn/tree and run 'rebuild'. You +can avoid this problem entirely by using 'dcommit'. We ignore all SVN properties except svn:executable. Too difficult to map them since we rely heavily on git write-tree being _exactly_ the diff --git a/Documentation/git-svnimport.txt b/Documentation/git-svnimport.txt index b1b87c2fcd..2c7c7dad54 100644 --- a/Documentation/git-svnimport.txt +++ b/Documentation/git-svnimport.txt @@ -15,6 +15,7 @@ SYNOPSIS [ -b branch_subdir ] [ -T trunk_subdir ] [ -t tag_subdir ] [ -s start_chg ] [ -m ] [ -r ] [ -M regex ] [ -I <ignorefile_name> ] [ -A <author_file> ] + [ -P <path_from_trunk> ] <SVN_repository_URL> [ <path> ] @@ -103,9 +104,17 @@ repository without -A. -l <max_rev>:: Specify a maximum revision number to pull. ++ +Formerly, this option controlled how many revisions to pull, +due to SVN memory leaks. (These have been worked around.) - Formerly, this option controlled how many revisions to pull, - due to SVN memory leaks. (These have been worked around.) +-P <path_from_trunk>:: + Partial import of the SVN tree. ++ +By default, the whole tree on the SVN trunk (/trunk) is imported. +'-P my/proj' will import starting only from '/trunk/my/proj'. +This option is useful when you want to import one project from a +svn repo which hosts multiple projects under the same trunk. -v:: Verbosity: let 'svnimport' report what it is doing. diff --git a/Documentation/git-symbolic-ref.txt b/Documentation/git-symbolic-ref.txt index 68ac6a65df..4bc35a1d4b 100644 --- a/Documentation/git-symbolic-ref.txt +++ b/Documentation/git-symbolic-ref.txt @@ -19,29 +19,22 @@ argument to see on which branch your working tree is on. Give two arguments, create or update a symbolic ref <name> to point at the given branch <ref>. -Traditionally, `.git/HEAD` is a symlink pointing at -`refs/heads/master`. When we want to switch to another branch, -we did `ln -sf refs/heads/newbranch .git/HEAD`, and when we want +A symbolic ref is a regular file that stores a string that +begins with `ref: refs/`. For example, your `.git/HEAD` is +a regular file whose contents is `ref: refs/heads/master`. + +NOTES +----- +In the past, `.git/HEAD` was a symbolic link pointing at +`refs/heads/master`. When we wanted to switch to another branch, +we did `ln -sf refs/heads/newbranch .git/HEAD`, and when we wanted to find out which branch we are on, we did `readlink .git/HEAD`. This was fine, and internally that is what still happens by default, but on platforms that do not have working symlinks, or that do not have the `readlink(1)` command, this was a bit cumbersome. On some platforms, `ln -sf` does not even work as -advertised (horrors). - -A symbolic ref can be a regular file that stores a string that -begins with `ref: refs/`. For example, your `.git/HEAD` *can* -be a regular file whose contents is `ref: refs/heads/master`. -This can be used on a filesystem that does not support symbolic -links. Instead of doing `readlink .git/HEAD`, `git-symbolic-ref -HEAD` can be used to find out which branch we are on. To point -the HEAD to `newbranch`, instead of `ln -sf refs/heads/newbranch -.git/HEAD`, `git-symbolic-ref HEAD refs/heads/newbranch` can be -used. - -Currently, .git/HEAD uses a regular file symbolic ref on Cygwin, -and everywhere else it is implemented as a symlink. This can be -changed at compilation time. +advertised (horrors). Therefore symbolic links are now deprecated +and symbolic refs are used by default. Author ------ diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 45476c2e41..48b82b86f8 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -9,7 +9,8 @@ git-tag - Create a tag object signed with GPG SYNOPSIS -------- [verse] -'git-tag' [-a | -s | -u <key-id>] [-f | -d] [-m <msg>] <name> [<head>] +'git-tag' [-a | -s | -u <key-id>] [-f | -d] [-m <msg> | -F <file>] + <name> [<head>] 'git-tag' -l [<pattern>] DESCRIPTION @@ -60,6 +61,9 @@ OPTIONS -m <msg>:: Use the given tag message (instead of prompting) +-F <file>:: + Take the tag message from the given file. Use '-' to + read the message from the standard input. Author ------ diff --git a/Documentation/git.txt b/Documentation/git.txt index 619d65685e..2a9e97dac1 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -351,6 +351,9 @@ gitlink:git-init-db[1]:: Creates an empty git object database, or reinitialize an existing one. +gitlink:git-merge-file[1]:: + Runs a threeway merge. + gitlink:git-merge-index[1]:: Runs a merge for files needing merging. @@ -639,11 +642,35 @@ git Commits git Diffs ~~~~~~~~~ 'GIT_DIFF_OPTS':: + Only valid setting is "--unified=??" or "-u??" to set the + number of context lines shown when a unified diff is created. + This takes precedence over any "-U" or "--unified" option + value passed on the git diff command line. + 'GIT_EXTERNAL_DIFF':: - see the "generating patches" section in : - gitlink:git-diff-index[1]; - gitlink:git-diff-files[1]; - gitlink:git-diff-tree[1] + When the environment variable 'GIT_EXTERNAL_DIFF' is set, the + program named by it is called, instead of the diff invocation + described above. For a path that is added, removed, or modified, + 'GIT_EXTERNAL_DIFF' is called with 7 parameters: + + path old-file old-hex old-mode new-file new-hex new-mode ++ +where: + + <old|new>-file:: are files GIT_EXTERNAL_DIFF can use to read the + contents of <old|new>, + <old|new>-hex:: are the 40-hexdigit SHA1 hashes, + <old|new>-mode:: are the octal representation of the file modes. + ++ +The file parameters can point at the user's working file +(e.g. `new-file` in "git-diff-files"), `/dev/null` (e.g. `old-file` +when a new file is added), or a temporary file (e.g. `old-file` in the +index). 'GIT_EXTERNAL_DIFF' should not worry about unlinking the +temporary file --- it is removed when 'GIT_EXTERNAL_DIFF' exits. ++ +For a path that is unmerged, 'GIT_EXTERNAL_DIFF' is called with 1 +parameter, <path>. other ~~~~~ diff --git a/Documentation/install-doc-quick.sh b/Documentation/install-doc-quick.sh new file mode 100755 index 0000000000..a64054948a --- /dev/null +++ b/Documentation/install-doc-quick.sh @@ -0,0 +1,31 @@ +#!/bin/sh +# This requires a branch named in $head +# (usually 'man' or 'html', provided by the git.git repository) +set -e +head="$1" +mandir="$2" +SUBDIRECTORY_OK=t +USAGE='<refname> <target directory>' +. git-sh-setup +export GIT_DIR + +test -z "$mandir" && usage +if ! git-rev-parse --verify "$head^0" >/dev/null; then + echo >&2 "head: $head does not exist in the current repository" + usage +fi + +GIT_INDEX_FILE=`pwd`/.quick-doc.index +export GIT_INDEX_FILE +rm -f "$GIT_INDEX_FILE" +git-read-tree $head +git-checkout-index -a -f --prefix="$mandir"/ + +if test -n "$GZ"; then + cd "$mandir" + for i in `git-ls-tree -r --name-only $head` + do + gzip < $i > $i.gz && rm $i + done +fi +rm -f "$GIT_INDEX_FILE" diff --git a/Documentation/technical/racy-git.txt b/Documentation/technical/racy-git.txt index 7597d04142..5030d9f2f8 100644 --- a/Documentation/technical/racy-git.txt +++ b/Documentation/technical/racy-git.txt @@ -4,7 +4,7 @@ Use of index and Racy git problem Background ---------- -The index is one of the most important data structure in git. +The index is one of the most important data structures in git. It represents a virtual working tree state by recording list of paths and their object names and serves as a staging area to write out the next tree object to be committed. The state is @@ -16,7 +16,7 @@ virtual working tree state in the index and the files in the working tree. The most obvious case is when the user asks `git diff` (or its low level implementation, `git diff-files`) or `git-ls-files --modified`. In addition, git internally checks -if the files in the working tree is different from what are +if the files in the working tree are different from what are recorded in the index to avoid stomping on local changes in them during patch application, switching branches, and merging. @@ -24,9 +24,9 @@ In order to speed up this comparison between the files in the working tree and the index entries, the index entries record the information obtained from the filesystem via `lstat(2)` system call when they were last updated. When checking if they differ, -git first runs `lstat(2)` on the files and compare the result +git first runs `lstat(2)` on the files and compares the result with this information (this is what was originally done by the -`ce_match_stat()` function, which the current code does in +`ce_match_stat()` function, but the current code does it in `ce_match_stat_basic()` function). If some of these "cached stat information" fields do not match, git can tell that the files are modified without even looking at their contents. @@ -53,8 +53,9 @@ Racy git There is one slight problem with the optimization based on the cached stat information. Consider this sequence: + : modify 'foo' $ git update-index 'foo' - : modify 'foo' in-place without changing its size + : modify 'foo' again, in-place, without changing its size The first `update-index` computes the object name of the contents of file `foo` and updates the index entry for `foo` @@ -62,7 +63,8 @@ along with the `struct stat` information. If the modification that follows it happens very fast so that the file's `st_mtime` timestamp does not change, after this sequence, the cached stat information the index entry records still exactly match what you -can obtain from the filesystem, but the file `foo` is modified. +would see in the filesystem, even though the file `foo` is now +different. This way, git can incorrectly think files in the working tree are unmodified even though they actually are. This is called the "racy git" problem (discovered by Pasky), and the entries @@ -87,7 +89,7 @@ the stat information from updated paths, `st_mtime` timestamp of it is usually the same as or newer than any of the paths the index contains. And no matter how quick the modification that follows `git update-index foo` finishes, the resulting -`st_mtime` timestamp on `foo` cannot get the timestamp earlier +`st_mtime` timestamp on `foo` cannot get a value earlier than the index file. Therefore, index entries that can be racily clean are limited to the ones that have the same timestamp as the index file itself. @@ -111,7 +113,7 @@ value, and falsely clean entry `foo` would not be caught by the timestamp comparison check done with the former logic anymore. The latter makes sure that the cached stat information for `foo` would never match with the file in the working tree, so later -checks by `ce_match_stat_basic()` would report the index entry +checks by `ce_match_stat_basic()` would report that the index entry does not match the file and git does not have to fall back on more expensive `ce_modified_check_fs()`. @@ -155,17 +157,16 @@ of the cached stat information. Avoiding runtime penalty ------------------------ -In order to avoid the above runtime penalty, the recent "master" -branch (post 1.4.2) has a code that makes sure the index file -gets timestamp newer than the youngest files in the index when +In order to avoid the above runtime penalty, post 1.4.2 git used +to have a code that made sure the index file +got timestamp newer than the youngest files in the index when there are many young files with the same timestamp as the resulting index file would otherwise would have by waiting before finishing writing the index file out. -I suspect that in practice the situation where many paths in the -index are all racily clean is quite rare. The only code paths -that can record recent timestamp for large number of paths I -know of are: +I suspected that in practice the situation where many paths in the +index are all racily clean was quite rare. The only code paths +that can record recent timestamp for large number of paths are: . Initial `git add .` of a large project. @@ -188,6 +189,7 @@ youngest file in the working tree. This means that in these cases there actually will not be any racily clean entry in the resulting index. -So in summary I think we should not worry about avoiding the -runtime penalty and get rid of the "wait before finishing -writing" code out. +Based on this discussion, the current code does not use the +"workaround" to avoid the runtime penalty that does not exist in +practice anymore. This was done with commit 0fc82cff on Aug 15, +2006. diff --git a/Documentation/tutorial-2.txt b/Documentation/tutorial-2.txt index 42b6e7d7d2..60e54777dc 100644 --- a/Documentation/tutorial-2.txt +++ b/Documentation/tutorial-2.txt @@ -18,17 +18,18 @@ Let's start a new project and create a small amount of history: $ mkdir test-project $ cd test-project $ git init-db -defaulting to local storage area +Initialized empty Git repository in .git/ $ echo 'hello world' > file.txt $ git add . $ git commit -a -m "initial commit" -Committing initial tree 92b8b694ffb1675e5975148e1121810081dbdffe +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 first commit -with? +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 @@ -38,13 +39,25 @@ 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--just cut-and-paste from the reply to the initial commit, to -save yourself typing all 40 hex digits: +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 92b8b694ffb1675e5975148e1121810081dbdffe -tree +$ 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 @@ -101,8 +114,7 @@ $ find .git/objects/ 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. We've seen a blob and a tree now, -so next we should look at a commit. +blob, a tree, a commit, or a tag. The simplest commit to find is the HEAD commit, which we can find from .git/HEAD: @@ -341,23 +353,23 @@ situation: ------------------------------------------------ $ git status # -# Updated but not checked in: +# Added but not yet committed: # (will commit) # # new file: closing.txt # # -# Changed but not updated: -# (use git-update-index to mark for commit) +# Changed but not added: +# (use "git add file1 file2" to include for commit) # # modified: file.txt # ------------------------------------------------ Since the current state of closing.txt is cached in the index file, -it is listed as "updated but not checked in". Since file.txt has +it is listed as "added but not yet 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 +it is marked "changed but not added". 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. diff --git a/Documentation/tutorial.txt b/Documentation/tutorial.txt index 1e4ddfbd11..cb808d924b 100644 --- a/Documentation/tutorial.txt +++ b/Documentation/tutorial.txt @@ -11,6 +11,18 @@ diff" with: $ man git-diff ------------------------------------------------ +It is a good idea to introduce yourself to git before doing any +operation. The easiest way to do so is: + +------------------------------------------------ +$ cat >~/.gitconfig <<\EOF +[user] + name = Your Name Comes Here + email = you@yourdomain.example.com +EOF +------------------------------------------------ + + Importing a new project ----------------------- @@ -26,12 +38,13 @@ $ git init-db Git will reply ------------------------------------------------ -defaulting to local storage area +Initialized empty Git repository in .git/ ------------------------------------------------ You've now initialized the working directory--you may notice a new directory created, named ".git". Tell git that you want it to track -every file under the current directory with +every file under the current directory with (notice the dot '.' +that means the current directory): ------------------------------------------------ $ git add . @@ -40,7 +53,7 @@ $ git add . Finally, ------------------------------------------------ -$ git commit -a +$ git commit ------------------------------------------------ will prompt you for a commit message, then record the current state @@ -55,11 +68,17 @@ $ git diff to review your changes. When you're done, ------------------------------------------------ -$ git commit -a +$ git commit file1 file2... ------------------------------------------------ will again prompt your for a message describing the change, and then -record the new versions of the modified files. +record the new versions of the files you listed. It is cumbersome +to list all files and you can say `-a` (which stands for 'all') +instead. + +------------------------------------------------ +$ git commit -a +------------------------------------------------ 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) @@ -68,14 +87,48 @@ 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. -To add a new file, first create the file, then ------------------------------------------------- -$ git add path/to/new/file ------------------------------------------------- +Git tracks content not files +---------------------------- + +With git you have to explicitly "add" all the changed _content_ you +want to commit together. This can be done in a few different ways: -then commit as usual. No special command is required when removing a -file; just remove it, then commit. +1) By using 'git add <file_spec>...' + + This can be performed multiple times before a commit. Note that this + is not only for adding new files. Even modified files must be + added to the set of changes about to be committed. The "git status" + command gives you a summary of what is included so far for the + next commit. When done you should use the 'git commit' command to + make it real. + + Note: don't forget to 'add' a file again if you modified it after the + first 'add' and before 'commit'. Otherwise only the previous added + state of that file will be committed. This is because git tracks + content, so what you're really 'add'ing to the commit is the *content* + of the file in the state it is in when you 'add' it. + +2) By using 'git commit -a' directly + + This is a quick way to automatically 'add' the content from all files + that were modified since the previous commit, and perform the actual + commit without having to separately 'add' them beforehand. This will + not add content from new files i.e. files that were never added before. + Those files still have to be added explicitly before performing a + commit. + +But here's a twist. If you do 'git commit <file1> <file2> ...' then only +the changes belonging to those explicitly specified files will be +committed, entirely bypassing the current "added" changes. Those "added" +changes will still remain available for a subsequent commit though. + +However, for normal usage you only have to remember 'git add' + 'git commit' +and/or 'git commit -a'. + + +Viewing the changelog +--------------------- At any point you can view the history of your changes using @@ -209,29 +262,28 @@ at /home/bob/myrepo. She does this with: ------------------------------------------------ $ cd /home/alice/project -$ git pull /home/bob/myrepo +$ git pull /home/bob/myrepo master ------------------------------------------------ -This actually pulls changes from the branch in Bob's repository named -"master". Alice could request a different branch by adding the name -of the branch to the end of the git pull command line. +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.) -This merges Bob's changes into her repository; "git log" will -now show the new commits. If Alice has made her own changes in the -meantime, then Bob's changes will be merged in, and she will need to -manually fix any conflicts. +The "pull" command thus performs two operations: it fetches changes +from a remote branch, then merges them into the current branch. -A more cautious Alice might wish to examine Bob's changes before -pulling them. She can do this by creating a temporary branch just -for the purpose of studying Bob's changes: +You can perform the first operation alone using the "git fetch" +command. For example, Alice could create a temporary branch just to +track Bob's changes, without merging them with her own, using: ------------------------------------- $ git fetch /home/bob/myrepo master:bob-incoming ------------------------------------- which fetches the changes from Bob's master branch into a new branch -named bob-incoming. (Unlike git pull, git fetch just fetches a copy -of Bob's line of development without doing any merging). Then +named bob-incoming. Then ------------------------------------- $ git log -p master..bob-incoming @@ -240,8 +292,8 @@ $ git log -p master..bob-incoming shows a list of all the changes that Bob made since he branched from Alice's master branch. -After examining those changes, and possibly fixing things, Alice can -pull the changes into her master branch: +After examining those changes, and possibly fixing things, Alice +could pull the changes into her master branch: ------------------------------------- $ git checkout master @@ -251,6 +303,18 @@ $ git pull . bob-incoming The last command is a pull from the "bob-incoming" branch in Alice's own repository. +Alice could also perform both steps at once with: + +------------------------------------- +$ git pull /home/bob/myrepo master:bob-incoming +------------------------------------- + +This is just like the "git pull /home/bob/myrepo master" that we saw +before, except that it also stores the unmerged changes from bob's +master branch in bob-incoming before merging them into Alice's +current branch. Note that git pull always merges into the current +branch, regardless of what else is given on the commandline. + Later, Bob can update his repo with Alice's latest changes using ------------------------------------- diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 4eac314f3a..21ff949ea2 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.4.4.1.GIT +DEF_VER=v1.4.5-rc0.GIT LF=' ' @@ -72,25 +72,6 @@ Issues of note: - expat library; git-http-push uses it for remote lock management over DAV. Similar to "curl" above, this is optional. - - "GNU diff" to generate patches. Of course, you don't _have_ to - generate patches if you don't want to, but let's face it, you'll - be wanting to. Or why did you get git in the first place? - - Non-GNU versions of the diff/patch programs don't generally support - the unified patch format (which is the one git uses), so you - really do want to get the GNU one. Trust me, you will want to - do that even if it wasn't for git. There's no point in living - in the dark ages any more. - - - "merge", the standard UNIX three-way merge program. It usually - comes with the "rcs" package on most Linux distributions, so if - you have a developer install you probably have it already, but a - "graphical user desktop" install might have left it out. - - You'll only need the merge program if you do development using - git, and if you only use git to track other peoples work you'll - never notice the lack of it. - - "wish", the Tcl/Tk windowing shell is used in gitk to show the history graphically @@ -99,9 +80,6 @@ Issues of note: - "perl" and POSIX-compliant shells are needed to use most of the barebone Porcelainish scripts. - - "python" 2.3 or more recent; if you have 2.3, you may need - to build with "make WITH_OWN_SUBPROCESS_PY=YesPlease". - - Some platform specific issues are dealt with Makefile rules, but depending on your specific installation, you may not have all the libraries/tools needed, or you may have @@ -69,7 +69,8 @@ all: # # Define NO_MMAP if you want to avoid mmap. # -# Define WITH_OWN_SUBPROCESS_PY if you want to use with python 2.3. +# Define NO_FAST_WORKING_DIRECTORY if accessing objects in pack files is +# generally faster on your platform than accessing the working directory. # # Define NO_IPV6 if you lack IPv6 support and getaddrinfo(). # @@ -78,13 +79,6 @@ all: # # Define NO_ICONV if your libc does not properly support iconv. # -# Define NO_ACCURATE_DIFF if your diff program at least sometimes misses -# a missing newline at the end of the file. -# -# Define COLLISION_CHECK below if you believe that SHA1's -# 1461501637330902918203684832716283019655932542976 hashes do not give you -# sufficient guarantee that no collisions between objects will ever happen. -# # Define USE_NSEC below if you want git to care about sub-second file mtimes # and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and # it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely @@ -93,6 +87,10 @@ all: # # Define USE_STDEV below if you want git to care about the underlying device # change being considered an inode change from the update-cache perspective. +# +# Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's +# MakeMaker (e.g. using ActiveState under Cygwin). +# GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE @$(SHELL_PATH) ./GIT-VERSION-GEN @@ -116,7 +114,6 @@ prefix = $(HOME) bindir = $(prefix)/bin gitexecdir = $(bindir) template_dir = $(prefix)/share/git-core/templates/ -GIT_PYTHON_DIR = $(prefix)/share/git-core/python # DESTDIR= # default configuration for gitweb @@ -135,7 +132,7 @@ GITWEB_FAVICON = git-favicon.png GITWEB_SITE_HEADER = GITWEB_SITE_FOOTER = -export prefix bindir gitexecdir template_dir GIT_PYTHON_DIR +export prefix bindir gitexecdir template_dir CC = gcc AR = ar @@ -173,18 +170,14 @@ SCRIPT_SH = \ git-lost-found.sh git-quiltimport.sh SCRIPT_PERL = \ + git-add--interactive.perl \ git-archimport.perl git-cvsimport.perl git-relink.perl \ - git-shortlog.perl git-rerere.perl \ git-cvsserver.perl \ git-svnimport.perl git-cvsexportcommit.perl \ git-send-email.perl git-svn.perl -SCRIPT_PYTHON = \ - git-merge-recursive-old.py - SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ $(patsubst %.perl,%,$(SCRIPT_PERL)) \ - $(patsubst %.py,%,$(SCRIPT_PYTHON)) \ git-cherry-pick git-status git-instaweb # ... and all the rest that could be moved out of bindir to gitexecdir @@ -227,12 +220,8 @@ endif ifndef PERL_PATH PERL_PATH = /usr/bin/perl endif -ifndef PYTHON_PATH - PYTHON_PATH = /usr/bin/python -endif -PYMODULES = \ - gitMergeCommon.py +export PERL_PATH LIB_FILE=libgit.a XDIFF_LIB=xdiff/lib.a @@ -241,7 +230,8 @@ LIB_H = \ archive.h blob.h cache.h commit.h csum-file.h delta.h grep.h \ diff.h object.h pack.h pkt-line.h quote.h refs.h list-objects.h sideband.h \ run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \ - tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h + tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h \ + utf8.h DIFF_OBJS = \ diff.o diff-lib.o diffcore-break.o diffcore-order.o \ @@ -260,7 +250,7 @@ LIB_OBJS = \ revision.o pager.o tree-walk.o xdiff-interface.o \ write_or_die.o trace.o list-objects.o grep.o \ alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \ - color.o wt-status.o archive-zip.o archive-tar.o shallow.o + color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o BUILTIN_OBJS = \ builtin-add.o \ @@ -288,6 +278,7 @@ BUILTIN_OBJS = \ builtin-ls-tree.o \ builtin-mailinfo.o \ builtin-mailsplit.o \ + builtin-merge-file.o \ builtin-mv.o \ builtin-name-rev.o \ builtin-pack-objects.o \ @@ -295,11 +286,14 @@ BUILTIN_OBJS = \ builtin-prune-packed.o \ builtin-push.o \ builtin-read-tree.o \ + builtin-reflog.o \ builtin-repo-config.o \ + builtin-rerere.o \ builtin-rev-list.o \ builtin-rev-parse.o \ builtin-rm.o \ builtin-runstatus.o \ + builtin-shortlog.o \ builtin-show-branch.o \ builtin-stripspace.o \ builtin-symbolic-ref.o \ @@ -334,18 +328,6 @@ ifeq ($(uname_S),Darwin) NEEDS_SSL_WITH_CRYPTO = YesPlease NEEDS_LIBICONV = YesPlease NO_STRLCPY = YesPlease - ifndef NO_FINK - ifeq ($(shell test -d /sw/lib && echo y),y) - BASIC_CFLAGS += -I/sw/include - BASIC_LDFLAGS += -L/sw/lib - endif - endif - ifndef NO_DARWIN_PORTS - ifeq ($(shell test -d /opt/local/lib && echo y),y) - BASIC_CFLAGS += -I/opt/local/include - BASIC_LDFLAGS += -L/opt/local/lib - endif - endif endif ifeq ($(uname_S),SunOS) NEEDS_SOCKET = YesPlease @@ -374,6 +356,7 @@ ifeq ($(uname_O),Cygwin) NO_SYMLINK_HEAD = YesPlease NEEDS_LIBICONV = YesPlease NO_C99_FORMAT = YesPlease + NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes # There are conflicting reports about this. # On some boxes NO_MMAP is needed, and not so elsewhere. # Try uncommenting this if you see things break -- YMMV. @@ -423,12 +406,17 @@ endif -include config.mak.autogen -include config.mak -ifdef WITH_OWN_SUBPROCESS_PY - PYMODULES += compat/subprocess.py -else - ifeq ($(NO_PYTHON),) - ifneq ($(shell $(PYTHON_PATH) -c 'import subprocess;print"OK"' 2>/dev/null),OK) - PYMODULES += compat/subprocess.py +ifeq ($(uname_S),Darwin) + ifndef NO_FINK + ifeq ($(shell test -d /sw/lib && echo y),y) + BASIC_CFLAGS += -I/sw/include + BASIC_LDFLAGS += -L/sw/lib + endif + endif + ifndef NO_DARWIN_PORTS + ifeq ($(shell test -d /opt/local/lib && echo y),y) + BASIC_CFLAGS += -I/opt/local/include + BASIC_LDFLAGS += -L/opt/local/lib endif endif endif @@ -520,6 +508,9 @@ ifdef NO_MMAP COMPAT_CFLAGS += -DNO_MMAP COMPAT_OBJS += compat/mmap.o endif +ifdef NO_FAST_WORKING_DIRECTORY + BASIC_CFLAGS += -DNO_FAST_WORKING_DIRECTORY +endif ifdef NO_IPV6 BASIC_CFLAGS += -DNO_IPV6 endif @@ -558,8 +549,8 @@ else endif endif endif -ifdef NO_ACCURATE_DIFF - BASIC_CFLAGS += -DNO_ACCURATE_DIFF +ifdef NO_PERL_MAKEMAKER + export NO_PERL_MAKEMAKER endif # Shell quote (do not use $(call) to accommodate ancient setups); @@ -574,8 +565,6 @@ prefix_SQ = $(subst ','\'',$(prefix)) SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) -PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH)) -GIT_PYTHON_DIR_SQ = $(subst ','\'',$(GIT_PYTHON_DIR)) LIBS = $(GITLIBS) $(EXTLIBS) @@ -592,8 +581,8 @@ export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi -all: perl/Makefile - $(MAKE) -C perl +all: + $(MAKE) -C perl PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all $(MAKE) -C templates strip: $(PROGRAMS) git$X @@ -622,12 +611,15 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ -e 's/@@NO_CURL@@/$(NO_CURL)/g' \ - -e 's/@@NO_PYTHON@@/$(NO_PYTHON)/g' \ $@.sh >$@+ chmod +x $@+ mv $@+ $@ -$(patsubst %.perl,%,$(SCRIPT_PERL)): perl/Makefile +$(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak + +perl/perl.mak: GIT-CFLAGS + $(MAKE) -C perl PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' $(@F) + $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl rm -f $@ $@+ INSTLIBDIR=`$(MAKE) -C perl -s --no-print-directory instlibdir` && \ @@ -644,15 +636,6 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl chmod +x $@+ mv $@+ $@ -$(patsubst %.py,%,$(SCRIPT_PYTHON)) : % : %.py GIT-CFLAGS - rm -f $@ $@+ - sed -e '1s|#!.*python|#!$(PYTHON_PATH_SQ)|' \ - -e 's|@@GIT_PYTHON_PATH@@|$(GIT_PYTHON_DIR_SQ)|g' \ - -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ - $@.py >$@+ - chmod +x $@+ - mv $@+ $@ - git-cherry-pick: git-revert cp $< $@+ mv $@+ $@ @@ -689,7 +672,6 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ -e 's/@@NO_CURL@@/$(NO_CURL)/g' \ - -e 's/@@NO_PYTHON@@/$(NO_PYTHON)/g' \ -e '/@@GITWEB_CGI@@/r gitweb/gitweb.cgi' \ -e '/@@GITWEB_CGI@@/d' \ -e '/@@GITWEB_CSS@@/r gitweb/gitweb.css' \ @@ -709,7 +691,6 @@ configure: configure.ac git$X git.spec \ $(patsubst %.sh,%,$(SCRIPT_SH)) \ $(patsubst %.perl,%,$(SCRIPT_PERL)) \ - $(patsubst %.py,%,$(SCRIPT_PYTHON)) \ : GIT-VERSION-FILE %.o: %.c GIT-CFLAGS @@ -759,7 +740,8 @@ $(DIFF_OBJS): diffcore.h $(LIB_FILE): $(LIB_OBJS) rm -f $@ && $(AR) rcs $@ $(LIB_OBJS) -XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o +XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \ + xdiff/xmerge.o $(XDIFF_OBJS): xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \ xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h @@ -783,7 +765,7 @@ tags: find . -name '*.[hcS]' -print | xargs ctags -a ### Detect prefix changes -TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):$(GIT_PYTHON_DIR_SQ):\ +TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):\ $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ) GIT-CFLAGS: .FORCE-GIT-CFLAGS @@ -799,7 +781,6 @@ GIT-CFLAGS: .FORCE-GIT-CFLAGS # However, the environment gets quite big, and some programs have problems # with that. -export NO_PYTHON export NO_SVN_TESTS test: all @@ -808,8 +789,8 @@ test: all test-date$X: test-date.c date.o ctype.o $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) test-date.c date.o ctype.o -test-delta$X: test-delta.c diff-delta.o patch-delta.o - $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $^ +test-delta$X: test-delta.o diff-delta.o patch-delta.o $(GITLIBS) + $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) test-dump-cache-tree$X: dump-cache-tree.o $(GITLIBS) $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) @@ -833,9 +814,7 @@ install: all $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL) git$X gitk '$(DESTDIR_SQ)$(bindir_SQ)' $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install - $(MAKE) -C perl install - $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)' - $(INSTALL) $(PYMODULES) '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)' + $(MAKE) -C perl prefix='$(prefix_SQ)' install if test 'z$(bindir_SQ)' != 'z$(gitexecdir_SQ)'; \ then \ ln -f '$(DESTDIR_SQ)$(bindir_SQ)/git$X' \ @@ -848,6 +827,8 @@ install: all install-doc: $(MAKE) -C Documentation install +quick-install-doc: + $(MAKE) -C Documentation quick-install @@ -905,8 +886,7 @@ clean: rm -f $(htmldocs).tar.gz $(manpages).tar.gz rm -f gitweb/gitweb.cgi $(MAKE) -C Documentation/ clean - [ ! -f perl/Makefile ] || $(MAKE) -C perl/ clean || $(MAKE) -C perl/ clean - rm -f perl/ppport.h perl/Makefile.old + $(MAKE) -C perl clean $(MAKE) -C templates/ clean $(MAKE) -C t/ clean rm -f GIT-VERSION-FILE GIT-CFLAGS @@ -922,7 +902,6 @@ check-docs:: case "$$v" in \ git-merge-octopus | git-merge-ours | git-merge-recursive | \ git-merge-resolve | git-merge-stupid | git-merge-recur | \ - git-merge-recursive-old | \ git-ssh-pull | git-ssh-push ) continue ;; \ esac ; \ test -f "Documentation/$$v.txt" || \ diff --git a/archive-tar.c b/archive-tar.c index ff0f6e2929..af47fdc955 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -1,7 +1,6 @@ /* * Copyright (c) 2005, 2006 Rene Scharfe */ -#include <time.h> #include "cache.h" #include "commit.h" #include "strbuf.h" diff --git a/archive-zip.c b/archive-zip.c index 36e922a1f2..f31b8ed823 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -1,7 +1,6 @@ /* * Copyright (c) 2006 Rene Scharfe */ -#include <time.h> #include "cache.h" #include "commit.h" #include "blob.h" @@ -1,6 +1,5 @@ #include "cache.h" #include "blob.h" -#include <stdlib.h> const char *blob_type = "blob"; diff --git a/builtin-add.c b/builtin-add.c index febb75ed99..8ed4a6a9f3 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -3,15 +3,14 @@ * * Copyright (C) 2006 Linus Torvalds */ -#include <fnmatch.h> - #include "cache.h" #include "builtin.h" #include "dir.h" +#include "exec_cmd.h" #include "cache-tree.h" static const char builtin_add_usage[] = -"git-add [-n] [-v] <filepattern>..."; +"git-add [-n] [-v] [-f] [--interactive] [--] <filepattern>..."; static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix) { @@ -27,7 +26,14 @@ static void prune_directory(struct dir_struct *dir, const char **pathspec, int p i = dir->nr; while (--i >= 0) { struct dir_entry *entry = *src++; - if (!match_pathspec(pathspec, entry->name, entry->len, prefix, seen)) { + int how = match_pathspec(pathspec, entry->name, entry->len, + prefix, seen); + /* + * ignored entries can be added with exact match, + * but not with glob nor recursive. + */ + if (!how || + (entry->ignored_entry && how != MATCHED_EXACTLY)) { free(entry); continue; } @@ -56,6 +62,8 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec) /* Set up the default git porcelain excludes */ memset(dir, 0, sizeof(*dir)); + if (pathspec) + dir->show_both = 1; dir->exclude_per_dir = ".gitignore"; path = git_path("info/exclude"); if (!access(path, R_OK)) @@ -83,20 +91,34 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec) static struct lock_file lock_file; +static const char ignore_warning[] = +"The following paths are ignored by one of your .gitignore files:\n"; + int cmd_add(int argc, const char **argv, const char *prefix) { int i, newfd; - int verbose = 0, show_only = 0; + int verbose = 0, show_only = 0, ignored_too = 0; const char **pathspec; struct dir_struct dir; + int add_interactive = 0; + + for (i = 1; i < argc; i++) { + if (!strcmp("--interactive", argv[i])) + add_interactive++; + } + if (add_interactive) { + const char *args[] = { "add--interactive", NULL }; + + if (add_interactive != 1 || argc != 2) + die("add --interactive does not take any parameters"); + execv_git_cmd(args); + exit(1); + } git_config(git_default_config); newfd = hold_lock_file_for_update(&lock_file, get_index_file(), 1); - if (read_cache() < 0) - die("index file corrupt"); - for (i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -110,12 +132,21 @@ int cmd_add(int argc, const char **argv, const char *prefix) show_only = 1; continue; } + if (!strcmp(arg, "-f")) { + ignored_too = 1; + continue; + } if (!strcmp(arg, "-v")) { verbose = 1; continue; } usage(builtin_add_usage); } + if (argc <= i) { + fprintf(stderr, "Nothing specified, nothing added.\n"); + fprintf(stderr, "Maybe you wanted to say 'git add .'?\n"); + return 0; + } pathspec = get_pathspec(prefix, argv + i); fill_directory(&dir, pathspec); @@ -123,6 +154,8 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (show_only) { const char *sep = "", *eof = ""; for (i = 0; i < dir.nr; i++) { + if (!ignored_too && dir.entries[i]->ignored_entry) + continue; printf("%s%s", sep, dir.entries[i]->name); sep = " "; eof = "\n"; @@ -131,6 +164,27 @@ int cmd_add(int argc, const char **argv, const char *prefix) return 0; } + if (read_cache() < 0) + die("index file corrupt"); + + if (!ignored_too) { + int has_ignored = -1; + for (i = 0; has_ignored < 0 && i < dir.nr; i++) + if (dir.entries[i]->ignored_entry) + has_ignored = i; + if (0 <= has_ignored) { + fprintf(stderr, ignore_warning); + for (i = has_ignored; i < dir.nr; i++) { + if (!dir.entries[i]->ignored_entry) + continue; + fprintf(stderr, "%s\n", dir.entries[i]->name); + } + fprintf(stderr, + "Use -f if you really want to add them.\n"); + exit(1); + } + } + for (i = 0; i < dir.nr; i++) add_file_to_index(dir.entries[i]->name, verbose); diff --git a/builtin-apply.c b/builtin-apply.c index 436d9e1880..1c35837068 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -6,7 +6,6 @@ * This applies patches on top of some (arbitrary) version of the SCM. * */ -#include <fnmatch.h> #include "cache.h" #include "cache-tree.h" #include "quote.h" diff --git a/builtin-archive.c b/builtin-archive.c index a8a1f079bf..391cf43911 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -2,7 +2,6 @@ * Copyright (c) 2006 Franck Bui-Huu * Copyright (c) 2006 Rene Scharfe */ -#include <time.h> #include "cache.h" #include "builtin.h" #include "archive.h" diff --git a/builtin-blame.c b/builtin-blame.c index 066dee743e..4a1accf13c 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -15,14 +15,12 @@ #include "revision.h" #include "xdiff-interface.h" -#include <time.h> -#include <sys/time.h> -#include <regex.h> - static char blame_usage[] = "git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S <revs-file>] [-M] [-C] [-C] [commit] [--] file\n" " -c, --compatibility Use the same output mode as git-annotate (Default: off)\n" +" -b Show blank SHA-1 for boundary commits (Default: off)\n" " -l, --long Show long commit SHA1 (Default: off)\n" +" --root Do not treat root commits as boundaries (Default: off)\n" " -t, --time Show raw timestamp (Default: off)\n" " -f, --show-name Show original filename (Default: auto)\n" " -n, --show-number Show original linenumber (Default: off)\n" @@ -36,6 +34,8 @@ static int longest_author; static int max_orig_digits; static int max_digits; static int max_score_digits; +static int show_root; +static int blank_boundary; #ifndef DEBUG #define DEBUG 0 @@ -1090,6 +1090,14 @@ static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt) if (!(commit->object.flags & UNINTERESTING) && !(revs->max_age != -1 && commit->date < revs->max_age)) pass_blame(sb, suspect, opt); + else { + commit->object.flags |= UNINTERESTING; + if (commit->object.parsed) + mark_parents_uninteresting(commit); + } + /* treat root commit as boundary */ + if (!commit->parents && !show_root) + commit->object.flags |= UNINTERESTING; /* Take responsibility for the remaining entries */ for (ent = sb->ent; ent; ent = ent->next) @@ -1273,6 +1281,8 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent) printf("committer-tz %s\n", ci.committer_tz); printf("filename %s\n", suspect->path); printf("summary %s\n", ci.summary); + if (suspect->commit->object.flags & UNINTERESTING) + printf("boundary\n"); } else if (suspect->commit->object.flags & MORE_THAN_ONE_PATH) printf("filename %s\n", suspect->path); @@ -1308,8 +1318,18 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) cp = nth_line(sb, ent->lno); for (cnt = 0; cnt < ent->num_lines; cnt++) { char ch; + int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8; + + if (suspect->commit->object.flags & UNINTERESTING) { + if (!blank_boundary) { + length--; + putchar('^'); + } + else + memset(hex, ' ', length); + } - printf("%.*s", (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8, hex); + printf("%.*s", length, hex); if (opt & OUTPUT_ANNOTATE_COMPAT) printf("\t(%10s\t%10s\t%d)", ci.author, format_time(ci.author_time, ci.author_tz, @@ -1435,14 +1455,14 @@ static void find_alignment(struct scoreboard *sb, int *option) struct commit_info ci; int num; + if (strcmp(suspect->path, sb->path)) + *option |= OUTPUT_SHOW_NAME; + num = strlen(suspect->path); + if (longest_file < num) + longest_file = num; if (!(suspect->commit->object.flags & METAINFO_SHOWN)) { suspect->commit->object.flags |= METAINFO_SHOWN; get_commit_info(suspect->commit, &ci, 1); - if (strcmp(suspect->path, sb->path)) - *option |= OUTPUT_SHOW_NAME; - num = strlen(suspect->path); - if (longest_file < num) - longest_file = num; num = strlen(ci.author); if (longest_author < num) longest_author = num; @@ -1626,6 +1646,19 @@ static void prepare_blame_range(struct scoreboard *sb, usage(blame_usage); } +static int git_blame_config(const char *var, const char *value) +{ + if (!strcmp(var, "blame.showroot")) { + show_root = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "blame.blankboundary")) { + blank_boundary = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value); +} + int cmd_blame(int argc, const char **argv, const char *prefix) { struct rev_info revs; @@ -1641,6 +1674,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) char type[10]; const char *bottomtop = NULL; + git_config(git_blame_config); save_commit_buffer = 0; opt = 0; @@ -1649,6 +1683,10 @@ int cmd_blame(int argc, const char **argv, const char *prefix) const char *arg = argv[i]; if (*arg != '-') break; + else if (!strcmp("-b", arg)) + blank_boundary = 1; + else if (!strcmp("--root", arg)) + show_root = 1; else if (!strcmp("-c", arg)) output_option |= OUTPUT_ANNOTATE_COMPAT; else if (!strcmp("-t", arg)) @@ -1787,6 +1825,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) /* Now we got rev and path. We do not want the path pruning * but we may want "bottom" processing. */ + argv[unk++] = "--"; /* terminate the rev name */ argv[unk] = NULL; init_revisions(&revs, NULL); diff --git a/builtin-branch.c b/builtin-branch.c index 22e3285a4c..745ee04d6e 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -6,55 +6,127 @@ */ #include "cache.h" +#include "color.h" #include "refs.h" #include "commit.h" #include "builtin.h" static const char builtin_branch_usage[] = -"git-branch (-d | -D) <branchname> | [-l] [-f] <branchname> [<start-point>] | [-r] | [-a]"; + "git-branch [-r] (-d | -D) <branchname> | [-l] [-f] <branchname> [<start-point>] | (-m | -M) [<oldbranch>] <newbranch> | [-r | -a] [-v [--abbrev=<length>]]"; +#define REF_UNKNOWN_TYPE 0x00 +#define REF_LOCAL_BRANCH 0x01 +#define REF_REMOTE_BRANCH 0x02 +#define REF_TAG 0x04 static const char *head; static unsigned char head_sha1[20]; -static int in_merge_bases(const unsigned char *sha1, - struct commit *rev1, - struct commit *rev2) +static int branch_use_color; +static char branch_colors[][COLOR_MAXLEN] = { + "\033[m", /* reset */ + "", /* PLAIN (normal) */ + "\033[31m", /* REMOTE (red) */ + "", /* LOCAL (normal) */ + "\033[32m", /* CURRENT (green) */ +}; +enum color_branch { + COLOR_BRANCH_RESET = 0, + COLOR_BRANCH_PLAIN = 1, + COLOR_BRANCH_REMOTE = 2, + COLOR_BRANCH_LOCAL = 3, + COLOR_BRANCH_CURRENT = 4, +}; + +static int parse_branch_color_slot(const char *var, int ofs) { - struct commit_list *bases, *b; - int ret = 0; + if (!strcasecmp(var+ofs, "plain")) + return COLOR_BRANCH_PLAIN; + if (!strcasecmp(var+ofs, "reset")) + return COLOR_BRANCH_RESET; + if (!strcasecmp(var+ofs, "remote")) + return COLOR_BRANCH_REMOTE; + if (!strcasecmp(var+ofs, "local")) + return COLOR_BRANCH_LOCAL; + if (!strcasecmp(var+ofs, "current")) + return COLOR_BRANCH_CURRENT; + die("bad config variable '%s'", var); +} - bases = get_merge_bases(rev1, rev2, 1); - for (b = bases; b; b = b->next) { - if (!hashcmp(sha1, b->item->object.sha1)) { - ret = 1; - break; - } +int git_branch_config(const char *var, const char *value) +{ + if (!strcmp(var, "color.branch")) { + branch_use_color = git_config_colorbool(var, value); + return 0; } + if (!strncmp(var, "color.branch.", 13)) { + int slot = parse_branch_color_slot(var, 13); + color_parse(value, var, branch_colors[slot]); + return 0; + } + return git_default_config(var, value); +} - free_commit_list(bases); - return ret; +const char *branch_get_color(enum color_branch ix) +{ + if (branch_use_color) + return branch_colors[ix]; + return ""; } -static void delete_branches(int argc, const char **argv, int force) +static int delete_branches(int argc, const char **argv, int force, int kinds) { - struct commit *rev, *head_rev; + struct commit *rev, *head_rev = head_rev; unsigned char sha1[20]; - char *name; + char *name = NULL; + const char *fmt, *remote; int i; + int ret = 0; + + switch (kinds) { + case REF_REMOTE_BRANCH: + fmt = "refs/remotes/%s"; + remote = "remote "; + force = 1; + break; + case REF_LOCAL_BRANCH: + fmt = "refs/heads/%s"; + remote = ""; + break; + default: + die("cannot use -a with -d"); + } - head_rev = lookup_commit_reference(head_sha1); + if (!force) { + head_rev = lookup_commit_reference(head_sha1); + if (!head_rev) + die("Couldn't look up commit object for HEAD"); + } for (i = 0; i < argc; i++) { - if (!strcmp(head, argv[i])) - die("Cannot delete the branch you are currently on."); + if (kinds == REF_LOCAL_BRANCH && !strcmp(head, argv[i])) { + error("Cannot delete the branch '%s' " + "which you are currently on.", argv[i]); + ret = 1; + continue; + } - name = xstrdup(mkpath("refs/heads/%s", argv[i])); - if (!resolve_ref(name, sha1, 1, NULL)) - die("Branch '%s' not found.", argv[i]); + if (name) + free(name); + + name = xstrdup(mkpath(fmt, argv[i])); + if (!resolve_ref(name, sha1, 1, NULL)) { + error("%sbranch '%s' not found.", + remote, argv[i]); + ret = 1; + continue; + } rev = lookup_commit_reference(sha1); - if (!rev || !head_rev) - die("Couldn't look up commit objects."); + if (!rev) { + error("Couldn't look up commit object for '%s'", name); + ret = 1; + continue; + } /* This checks whether the merge bases of branch and * HEAD contains branch -- which means that the HEAD @@ -62,35 +134,38 @@ static void delete_branches(int argc, const char **argv, int force) */ if (!force && - !in_merge_bases(sha1, rev, head_rev)) { - fprintf(stderr, - "The branch '%s' is not a strict subset of your current HEAD.\n" - "If you are sure you want to delete it, run 'git branch -D %s'.\n", - argv[i], argv[i]); - exit(1); + !in_merge_bases(rev, head_rev)) { + error("The branch '%s' is not a strict subset of " + "your current HEAD.\n" + "If you are sure you want to delete it, " + "run 'git branch -D %s'.", argv[i], argv[i]); + ret = 1; + continue; } - if (delete_ref(name, sha1)) - printf("Error deleting branch '%s'\n", argv[i]); - else - printf("Deleted branch %s.\n", argv[i]); + if (delete_ref(name, sha1)) { + error("Error deleting %sbranch '%s'", remote, + argv[i]); + ret = 1; + } else + printf("Deleted %sbranch %s.\n", remote, argv[i]); - free(name); } -} -#define REF_UNKNOWN_TYPE 0x00 -#define REF_LOCAL_BRANCH 0x01 -#define REF_REMOTE_BRANCH 0x02 -#define REF_TAG 0x04 + if (name) + free(name); + + return(ret); +} struct ref_item { char *name; unsigned int kind; + unsigned char sha1[20]; }; struct ref_list { - int index, alloc; + int index, alloc, maxwidth; struct ref_item *list; int kinds; }; @@ -100,6 +175,7 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, struct ref_list *ref_list = (struct ref_list*)(cb_data); struct ref_item *newitem; int kind = REF_UNKNOWN_TYPE; + int len; /* Detect kind */ if (!strncmp(refname, "refs/heads/", 11)) { @@ -128,6 +204,10 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, newitem = &(ref_list->list[ref_list->index++]); newitem->name = xstrdup(refname); newitem->kind = kind; + hashcpy(newitem->sha1, sha1); + len = strlen(newitem->name); + if (len > ref_list->maxwidth) + ref_list->maxwidth = len; return 0; } @@ -151,11 +231,29 @@ static int ref_cmp(const void *r1, const void *r2) return strcmp(c1->name, c2->name); } -static void print_ref_list(int kinds) +static void print_ref_info(const unsigned char *sha1, int abbrev) +{ + struct commit *commit; + char subject[256]; + + + commit = lookup_commit(sha1); + if (commit && !parse_commit(commit)) + pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0, + subject, sizeof(subject), 0, + NULL, NULL, 0); + else + strcpy(subject, " **** invalid ref ****"); + + printf(" %s %s\n", find_unique_abbrev(sha1, abbrev), subject); +} + +static void print_ref_list(int kinds, int verbose, int abbrev) { int i; char c; struct ref_list ref_list; + int color; memset(&ref_list, 0, sizeof(ref_list)); ref_list.kinds = kinds; @@ -164,12 +262,38 @@ static void print_ref_list(int kinds) qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp); for (i = 0; i < ref_list.index; i++) { + switch( ref_list.list[i].kind ) { + case REF_LOCAL_BRANCH: + color = COLOR_BRANCH_LOCAL; + break; + case REF_REMOTE_BRANCH: + color = COLOR_BRANCH_REMOTE; + break; + default: + color = COLOR_BRANCH_PLAIN; + break; + } + c = ' '; if (ref_list.list[i].kind == REF_LOCAL_BRANCH && - !strcmp(ref_list.list[i].name, head)) + !strcmp(ref_list.list[i].name, head)) { c = '*'; + color = COLOR_BRANCH_CURRENT; + } - printf("%c %s\n", c, ref_list.list[i].name); + if (verbose) { + printf("%c %s%-*s%s", c, + branch_get_color(color), + ref_list.maxwidth, + ref_list.list[i].name, + branch_get_color(COLOR_BRANCH_RESET)); + print_ref_info(ref_list.list[i].sha1, abbrev); + } + else + printf("%c %s%s%s\n", c, + branch_get_color(color), + ref_list.list[i].name, + branch_get_color(COLOR_BRANCH_RESET)); } free_ref_list(&ref_list); @@ -212,14 +336,47 @@ static void create_branch(const char *name, const char *start, die("Failed to write ref: %s.", strerror(errno)); } +static void rename_branch(const char *oldname, const char *newname, int force) +{ + char oldref[PATH_MAX], newref[PATH_MAX], logmsg[PATH_MAX*2 + 100]; + unsigned char sha1[20]; + + if (snprintf(oldref, sizeof(oldref), "refs/heads/%s", oldname) > sizeof(oldref)) + die("Old branchname too long"); + + if (check_ref_format(oldref)) + die("Invalid branch name: %s", oldref); + + if (snprintf(newref, sizeof(newref), "refs/heads/%s", newname) > sizeof(newref)) + die("New branchname too long"); + + if (check_ref_format(newref)) + die("Invalid branch name: %s", newref); + + if (resolve_ref(newref, sha1, 1, NULL) && !force) + die("A branch named '%s' already exists.", newname); + + snprintf(logmsg, sizeof(logmsg), "Branch: renamed %s to %s", + oldref, newref); + + if (rename_ref(oldref, newref, logmsg)) + die("Branch rename failed"); + + if (!strcmp(oldname, head) && create_symref("HEAD", newref)) + die("Branch renamed to %s, but HEAD is not updated!", newname); +} + int cmd_branch(int argc, const char **argv, const char *prefix) { int delete = 0, force_delete = 0, force_create = 0; + int rename = 0, force_rename = 0; + int verbose = 0, abbrev = DEFAULT_ABBREV; int reflog = 0; int kinds = REF_LOCAL_BRANCH; int i; - git_config(git_default_config); + setup_ident(); + git_config(git_branch_config); for (i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -243,6 +400,15 @@ int cmd_branch(int argc, const char **argv, const char *prefix) force_create = 1; continue; } + if (!strcmp(arg, "-m")) { + rename = 1; + continue; + } + if (!strcmp(arg, "-M")) { + rename = 1; + force_rename = 1; + continue; + } if (!strcmp(arg, "-r")) { kinds = REF_REMOTE_BRANCH; continue; @@ -255,9 +421,29 @@ int cmd_branch(int argc, const char **argv, const char *prefix) reflog = 1; continue; } + if (!strncmp(arg, "--abbrev=", 9)) { + abbrev = atoi(arg+9); + continue; + } + if (!strcmp(arg, "-v")) { + verbose = 1; + continue; + } + if (!strcmp(arg, "--color")) { + branch_use_color = 1; + continue; + } + if (!strcmp(arg, "--no-color")) { + branch_use_color = 0; + continue; + } usage(builtin_branch_usage); } + if ((delete && rename) || (delete && force_create) || + (rename && force_create)) + usage(builtin_branch_usage); + head = xstrdup(resolve_ref("HEAD", head_sha1, 0, NULL)); if (!head) die("Failed to resolve HEAD as a valid ref."); @@ -266,9 +452,13 @@ int cmd_branch(int argc, const char **argv, const char *prefix) head += 11; if (delete) - delete_branches(argc - i, argv + i, force_delete); + return delete_branches(argc - i, argv + i, force_delete, kinds); else if (i == argc) - print_ref_list(kinds); + print_ref_list(kinds, verbose, abbrev); + else if (rename && (i == argc - 1)) + rename_branch(head, argv[i], force_rename); + else if (rename && (i == argc - 2)) + rename_branch(argv[i], argv[i + 1], force_rename); else if (i == argc - 1) create_branch(argv[i], head, force_create, reflog); else if (i == argc - 2) diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c index e2e690a1ae..f641787988 100644 --- a/builtin-commit-tree.c +++ b/builtin-commit-tree.c @@ -7,6 +7,7 @@ #include "commit.h" #include "tree.h" #include "builtin.h" +#include "utf8.h" #define BLOCKING (1ul << 14) @@ -32,7 +33,7 @@ static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...) len = vsnprintf(one_line, sizeof(one_line), fmt, args); va_end(args); size = *sizep; - newsize = size + len; + newsize = size + len + 1; alloc = (size + 32767) & ~32767; buf = *bufp; if (newsize > alloc) { @@ -40,7 +41,7 @@ static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...) buf = xrealloc(buf, alloc); *bufp = buf; } - *sizep = newsize; + *sizep = newsize - 1; memcpy(buf + size, one_line, len); } @@ -77,6 +78,11 @@ static int new_parent(int idx) return 1; } +static const char commit_utf8_warn[] = +"Warning: commit message does not conform to UTF-8.\n" +"You may want to amend it after fixing the message, or set the config\n" +"variable i18n.commitencoding to the encoding your project uses.\n"; + int cmd_commit_tree(int argc, const char **argv, const char *prefix) { int i; @@ -101,14 +107,15 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) a = argv[i]; b = argv[i+1]; if (!b || strcmp(a, "-p")) usage(commit_tree_usage); + + if (parents >= MAXPARENT) + die("Too many parents (%d max)", MAXPARENT); if (get_sha1(b, parent_sha1[parents])) die("Not a valid object name %s", b); check_valid(parent_sha1[parents], commit_type); if (new_parent(parents)) parents++; } - if (!parents) - fprintf(stderr, "Committing initial tree %s\n", argv[1]); init_buffer(&buffer, &size); add_buffer(&buffer, &size, "tree %s\n", sha1_to_hex(tree_sha1)); @@ -129,6 +136,11 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) while (fgets(comment, sizeof(comment), stdin) != NULL) add_buffer(&buffer, &size, "%s", comment); + /* And check the encoding */ + buffer[size] = '\0'; + if (!strcmp(git_commit_encoding, "utf-8") && !is_utf8(buffer)) + fprintf(stderr, commit_utf8_warn); + if (!write_sha1_file(buffer, size, commit_type, commit_sha1)) { printf("%s\n", sha1_to_hex(commit_sha1)); return 0; diff --git a/builtin-count-objects.c b/builtin-count-objects.c index 73c5982423..f5b22bb80e 100644 --- a/builtin-count-objects.c +++ b/builtin-count-objects.c @@ -105,16 +105,19 @@ int cmd_count_objects(int ac, const char **av, const char *prefix) } if (verbose) { struct packed_git *p; + unsigned long num_pack = 0; if (!packed_git) prepare_packed_git(); for (p = packed_git; p; p = p->next) { if (!p->pack_local) continue; packed += num_packed_objects(p); + num_pack++; } printf("count: %lu\n", loose); printf("size: %lu\n", loose_size / 2); printf("in-pack: %lu\n", packed); + printf("packs: %lu\n", num_pack); printf("prune-packable: %lu\n", packed_loose); printf("garbage: %lu\n", garbage); } diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c index 227aa3cd7f..af72a12a57 100644 --- a/builtin-for-each-ref.c +++ b/builtin-for-each-ref.c @@ -6,7 +6,6 @@ #include "tree.h" #include "blob.h" #include "quote.h" -#include <fnmatch.h> /* Quoting styles */ #define QUOTE_NONE 0 diff --git a/builtin-grep.c b/builtin-grep.c index ad7dc00cde..3b1b1cbbfa 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -10,10 +10,7 @@ #include "tag.h" #include "tree-walk.h" #include "builtin.h" -#include <regex.h> #include "grep.h" -#include <fnmatch.h> -#include <sys/wait.h> /* * git grep pathspecs are somewhat different from diff-tree pathspecs; @@ -268,7 +265,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached) for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; char *name; - if (ce_stage(ce) || !S_ISREG(ntohl(ce->ce_mode))) + if (!S_ISREG(ntohl(ce->ce_mode))) continue; if (!pathspec_matches(paths, ce->name)) continue; @@ -280,12 +277,19 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached) memcpy(name + 2, ce->name, len + 1); } argv[argc++] = name; - if (argc < MAXARGS) + if (argc < MAXARGS && !ce_stage(ce)) continue; status = exec_grep(argc, argv); if (0 < status) hit = 1; argc = nr; + if (ce_stage(ce)) { + do { + i++; + } while (i < active_nr && + !strcmp(ce->name, active_cache[i]->name)); + i--; /* compensate for loop control */ + } } if (argc > nr) { status = exec_grep(argc, argv); @@ -316,14 +320,24 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached) for (nr = 0; nr < active_nr; nr++) { struct cache_entry *ce = active_cache[nr]; - if (ce_stage(ce) || !S_ISREG(ntohl(ce->ce_mode))) + if (!S_ISREG(ntohl(ce->ce_mode))) continue; if (!pathspec_matches(paths, ce->name)) continue; - if (cached) + if (cached) { + if (ce_stage(ce)) + continue; hit |= grep_sha1(opt, ce->sha1, ce->name, 0); + } else hit |= grep_file(opt, ce->name); + if (ce_stage(ce)) { + do { + nr++; + } while (nr < active_nr && + !strcmp(ce->name, active_cache[nr]->name)); + nr--; /* compensate for loop control */ + } } free_grep_patterns(opt); return hit; diff --git a/builtin-init-db.c b/builtin-init-db.c index 235a0ee48f..01f366ad0b 100644 --- a/builtin-init-db.c +++ b/builtin-init-db.c @@ -124,8 +124,11 @@ static void copy_templates(const char *git_dir, int len, const char *template_di int template_len; DIR *dir; - if (!template_dir) - template_dir = DEFAULT_GIT_TEMPLATE_DIR; + if (!template_dir) { + template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT); + if (!template_dir) + template_dir = DEFAULT_GIT_TEMPLATE_DIR; + } strcpy(template_path, template_dir); template_len = strlen(template_path); if (template_path[template_len-1] != '/') { @@ -164,13 +167,14 @@ static void copy_templates(const char *git_dir, int len, const char *template_di closedir(dir); } -static void create_default_files(const char *git_dir, const char *template_path) +static int create_default_files(const char *git_dir, const char *template_path) { unsigned len = strlen(git_dir); static char path[PATH_MAX]; unsigned char sha1[20]; struct stat st1; char repo_version_string[10]; + int reinit; if (len > sizeof(path)-50) die("insane git directory %s", git_dir); @@ -218,7 +222,8 @@ static void create_default_files(const char *git_dir, const char *template_path) * branch, if it does not exist yet. */ strcpy(path + len, "HEAD"); - if (read_ref("HEAD", sha1) < 0) { + reinit = !read_ref("HEAD", sha1); + if (!reinit) { if (create_symref("HEAD", "refs/heads/master") < 0) exit(1); } @@ -239,6 +244,11 @@ static void create_default_files(const char *git_dir, const char *template_path) git_config_set("core.filemode", filemode ? "true" : "false"); } + + /* Enable logAllRefUpdates if a working tree is attached */ + if (!is_bare_git_dir(git_dir)) + git_config_set("core.logallrefupdates", "true"); + return reinit; } static const char init_db_usage[] = @@ -256,7 +266,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) const char *sha1_dir; const char *template_dir = NULL; char *path; - int len, i; + int len, i, reinit; for (i = 1; i < argc; i++, argv++) { const char *arg = argv[1]; @@ -274,10 +284,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) * Set up the default .git directory contents */ git_dir = getenv(GIT_DIR_ENVIRONMENT); - if (!git_dir) { + if (!git_dir) git_dir = DEFAULT_GIT_DIR_ENVIRONMENT; - fprintf(stderr, "defaulting to local storage area\n"); - } safe_create_dir(git_dir, 0); /* Check to see if the repository version is right. @@ -287,7 +295,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) */ check_repository_format(); - create_default_files(git_dir, template_dir); + reinit = create_default_files(git_dir, template_dir); /* * And set up the object store. @@ -314,5 +322,10 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) git_config_set("receive.denyNonFastforwards", "true"); } + printf("%s%s Git repository in %s/\n", + reinit ? "Reinitialized existing" : "Initialized empty", + shared_repository ? " shared" : "", + git_dir); + return 0; } diff --git a/builtin-log.c b/builtin-log.c index 7acf5d3b0c..8df3c1394a 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -10,8 +10,7 @@ #include "revision.h" #include "log-tree.h" #include "builtin.h" -#include <time.h> -#include <sys/time.h> +#include "tag.h" static int default_show_root = 1; @@ -71,9 +70,43 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix) return cmd_log_walk(&rev); } +static int show_object(const unsigned char *sha1, int suppress_header) +{ + unsigned long size; + char type[20]; + char *buf = read_sha1_file(sha1, type, &size); + int offset = 0; + + if (!buf) + return error("Could not read object %s", sha1_to_hex(sha1)); + + if (suppress_header) + while (offset < size && buf[offset++] != '\n') { + int new_offset = offset; + while (new_offset < size && buf[new_offset++] != '\n') + ; /* do nothing */ + offset = new_offset; + } + + if (offset < size) + fwrite(buf + offset, size - offset, 1, stdout); + free(buf); + return 0; +} + +static int show_tree_object(const unsigned char *sha1, + const char *base, int baselen, + const char *pathname, unsigned mode, int stage) +{ + printf("%s%s\n", pathname, S_ISDIR(mode) ? "/" : ""); + return 0; +} + int cmd_show(int argc, const char **argv, const char *prefix) { struct rev_info rev; + struct object_array_entry *objects; + int i, count, ret = 0; git_config(git_log_config); init_revisions(&rev, prefix); @@ -85,7 +118,52 @@ int cmd_show(int argc, const char **argv, const char *prefix) rev.ignore_merges = 0; rev.no_walk = 1; cmd_log_init(argc, argv, prefix, &rev); - return cmd_log_walk(&rev); + + count = rev.pending.nr; + objects = rev.pending.objects; + for (i = 0; i < count && !ret; i++) { + struct object *o = objects[i].item; + const char *name = objects[i].name; + switch (o->type) { + case OBJ_BLOB: + ret = show_object(o->sha1, 0); + break; + case OBJ_TAG: { + struct tag *t = (struct tag *)o; + + printf("%stag %s%s\n\n", + diff_get_color(rev.diffopt.color_diff, + DIFF_COMMIT), + t->tag, + diff_get_color(rev.diffopt.color_diff, + DIFF_RESET)); + ret = show_object(o->sha1, 1); + objects[i].item = (struct object *)t->tagged; + i--; + break; + } + case OBJ_TREE: + printf("%stree %s%s\n\n", + diff_get_color(rev.diffopt.color_diff, + DIFF_COMMIT), + name, + diff_get_color(rev.diffopt.color_diff, + DIFF_RESET)); + read_tree_recursive((struct tree *)o, "", 0, 0, NULL, + show_tree_object); + break; + case OBJ_COMMIT: + rev.pending.nr = rev.pending.alloc = 0; + rev.pending.objects = NULL; + add_object_array(o, name, &rev.pending); + ret = cmd_log_walk(&rev); + break; + default: + ret = error("Unknown type: %d", o->type); + } + } + free(objects); + return ret; } int cmd_log(int argc, const char **argv, const char *prefix) @@ -118,7 +196,7 @@ static int git_format_config(const char *var, const char *value) strcat(extra_headers, value); return 0; } - if (!strcmp(var, "diff.color")) { + if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) { return 0; } return git_log_config(var, value); diff --git a/builtin-ls-files.c b/builtin-ls-files.c index ad8c41e731..21c2a6e2d9 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -5,8 +5,6 @@ * * Copyright (C) Linus Torvalds, 2005 */ -#include <fnmatch.h> - #include "cache.h" #include "quote.h" #include "dir.h" @@ -487,10 +485,14 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) for (num = 0; pathspec[num]; num++) { if (ps_matched[num]) continue; - error("pathspec '%s' did not match any.", + error("pathspec '%s' did not match any file(s) known to git.", pathspec[num] + prefix_offset); errors++; } + + if (errors) + fprintf(stderr, "Did you forget to 'git add'?\n"); + return errors ? 1 : 0; } diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index b8d7dbc0b7..e6472293d4 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -2,15 +2,6 @@ * Another stupid program, this one parsing the headers of an * email to figure out authorship and subject */ -#define _GNU_SOURCE -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <ctype.h> -#ifndef NO_ICONV -#include <iconv.h> -#endif -#include "git-compat-util.h" #include "cache.h" #include "builtin.h" diff --git a/builtin-mailsplit.c b/builtin-mailsplit.c index 91a699d34d..3bca855aae 100644 --- a/builtin-mailsplit.c +++ b/builtin-mailsplit.c @@ -4,13 +4,6 @@ * It just splits a mbox into a list of files: "0001" "0002" .. * so you can process them further from there. */ -#include <unistd.h> -#include <stdlib.h> -#include <fcntl.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <string.h> -#include <stdio.h> #include "cache.h" #include "builtin.h" diff --git a/builtin-merge-file.c b/builtin-merge-file.c new file mode 100644 index 0000000000..9135773908 --- /dev/null +++ b/builtin-merge-file.c @@ -0,0 +1,63 @@ +#include "cache.h" +#include "xdiff/xdiff.h" +#include "xdiff-interface.h" + +static const char merge_file_usage[] = +"git merge-file [-p | --stdout] [-q | --quiet] [-L name1 [-L orig [-L name2]]] file1 orig_file file2"; + +int cmd_merge_file(int argc, char **argv, char **envp) +{ + char *names[3]; + mmfile_t mmfs[3]; + mmbuffer_t result = {NULL, 0}; + xpparam_t xpp = {XDF_NEED_MINIMAL}; + int ret = 0, i = 0, to_stdout = 0; + + while (argc > 4) { + if (!strcmp(argv[1], "-L") && i < 3) { + names[i++] = argv[2]; + argc--; + argv++; + } else if (!strcmp(argv[1], "-p") || + !strcmp(argv[1], "--stdout")) + to_stdout = 1; + else if (!strcmp(argv[1], "-q") || + !strcmp(argv[1], "--quiet")) + freopen("/dev/null", "w", stderr); + else + usage(merge_file_usage); + argc--; + argv++; + } + + if (argc != 4) + usage(merge_file_usage); + + for (; i < 3; i++) + names[i] = argv[i + 1]; + + for (i = 0; i < 3; i++) + if (read_mmfile(mmfs + i, argv[i + 1])) + return -1; + + ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2], + &xpp, XDL_MERGE_ZEALOUS, &result); + + for (i = 0; i < 3; i++) + free(mmfs[i].ptr); + + if (ret >= 0) { + char *filename = argv[1]; + FILE *f = to_stdout ? stdout : fopen(filename, "wb"); + + if (!f) + ret = error("Could not open %s for writing", filename); + else if (fwrite(result.ptr, result.size, 1, f) != 1) + ret = error("Could not write to %s", filename); + else if (fclose(f)) + ret = error("Could not close %s", filename); + free(result.ptr); + } + + return ret; +} diff --git a/builtin-mv.c b/builtin-mv.c index 54dd3bfe8a..737af350b8 100644 --- a/builtin-mv.c +++ b/builtin-mv.c @@ -3,8 +3,6 @@ * * Copyright (C) 2006 Johannes Schindelin */ -#include <fnmatch.h> - #include "cache.h" #include "builtin.h" #include "dir.h" @@ -146,21 +144,24 @@ int cmd_mv(int argc, const char **argv, const char *prefix) && lstat(dst, &st) == 0) bad = "cannot move directory over file"; else if (src_is_dir) { + const char *src_w_slash = add_slash(src); + int len_w_slash = length + 1; int first, last; modes[i] = WORKING_DIRECTORY; - first = cache_name_pos(src, length); + first = cache_name_pos(src_w_slash, len_w_slash); if (first >= 0) - die ("Huh? %s/ is in index?", src); + die ("Huh? %.*s is in index?", + len_w_slash, src_w_slash); first = -1 - first; for (last = first; last < active_nr; last++) { const char *path = active_cache[last]->name; - if (strncmp(path, src, length) - || path[length] != '/') + if (strncmp(path, src_w_slash, len_w_slash)) break; } + free((char *)src_w_slash); if (last - first < 1) bad = "source directory is empty"; diff --git a/builtin-name-rev.c b/builtin-name-rev.c index 618aa314d2..b4f15cc38a 100644 --- a/builtin-name-rev.c +++ b/builtin-name-rev.c @@ -1,4 +1,3 @@ -#include <stdlib.h> #include "builtin.h" #include "cache.h" #include "commit.h" diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 753bcd57b0..9e15beb3ba 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -12,14 +12,12 @@ #include "diff.h" #include "revision.h" #include "list-objects.h" -#include <sys/time.h> -#include <signal.h> static const char pack_usage[] = "\ git-pack-objects [{ -q | --progress | --all-progress }] \n\ [--local] [--incremental] [--window=N] [--depth=N] \n\ [--no-reuse-delta] [--delta-base-offset] [--non-empty] \n\ - [--revs [--unpacked | --all]*] [--stdout | base-name] \n\ + [--revs [--unpacked | --all]*] [--reflog] [--stdout | base-name] \n\ [<ref-list | <object-list]"; struct object_entry { @@ -514,6 +512,8 @@ static void write_pack_file(void) if (do_progress) fputc('\n', stderr); done: + if (written != nr_result) + die("wrote %d objects while expecting %d", written, nr_result); sha1close(f, pack_file_sha1, 1); } @@ -1575,6 +1575,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) } if (!strcmp("--unpacked", arg) || !strncmp("--unpacked=", arg, 11) || + !strcmp("--reflog", arg) || !strcmp("--all", arg)) { use_internal_rev_list = 1; if (ARRAY_SIZE(rp_av) - 1 <= rp_ac) @@ -1662,7 +1663,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) } } if (progress) - fprintf(stderr, "Total %d, written %d (delta %d), reused %d (delta %d)\n", - nr_result, written, written_delta, reused, reused_delta); + fprintf(stderr, "Total %d (delta %d), reused %d (delta %d)\n", + written, written_delta, reused, reused_delta); return 0; } diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c index 042d2718f9..8dc5b9efff 100644 --- a/builtin-pack-refs.c +++ b/builtin-pack-refs.c @@ -1,5 +1,7 @@ #include "cache.h" #include "refs.h" +#include "object.h" +#include "tag.h" static const char builtin_pack_refs_usage[] = "git-pack-refs [--all] [--prune]"; @@ -29,12 +31,26 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data) { struct pack_refs_cb_data *cb = cb_data; + int is_tag_ref; - if (!cb->all && strncmp(path, "refs/tags/", 10)) - return 0; /* Do not pack the symbolic refs */ - if (!(flags & REF_ISSYMREF)) - fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path); + if ((flags & REF_ISSYMREF)) + return 0; + is_tag_ref = !strncmp(path, "refs/tags/", 10); + if (!cb->all && !is_tag_ref) + return 0; + + fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path); + if (is_tag_ref) { + struct object *o = parse_object(sha1); + if (o->type == OBJ_TAG) { + o = deref_tag(o, path, 0); + if (o) + fprintf(cb->refs_file, "^%s\n", + sha1_to_hex(o->sha1)); + } + } + if (cb->prune && !do_not_prune(flags)) { int namelen = strlen(path) + 1; struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen); @@ -95,6 +111,10 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix) if (!cbdata.refs_file) die("unable to create ref-pack file structure (%s)", strerror(errno)); + + /* perhaps other traits later as well */ + fprintf(cbdata.refs_file, "# pack-refs with: peeled \n"); + for_each_ref(handle_one_ref, &cbdata); fflush(cbdata.refs_file); fsync(fd); diff --git a/builtin-prune.c b/builtin-prune.c index 8591d28b8e..00a53b3647 100644 --- a/builtin-prune.c +++ b/builtin-prune.c @@ -181,12 +181,28 @@ static void walk_commit_list(struct rev_info *revs) } } +static int add_one_reflog_ent(unsigned char *osha1, unsigned char *nsha1, char *datail, void *cb_data) +{ + struct object *object; + + object = parse_object(osha1); + if (object) + add_pending_object(&revs, object, ""); + object = parse_object(nsha1); + if (object) + add_pending_object(&revs, object, ""); + return 0; +} + static int add_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) { struct object *object = parse_object(sha1); if (!object) die("bad object ref: %s:%s", path, sha1_to_hex(sha1)); add_pending_object(&revs, object, ""); + + for_each_reflog_ent(path, add_one_reflog_ent, NULL); + return 0; } diff --git a/builtin-push.c b/builtin-push.c index d23974e708..b7412e8293 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -57,11 +57,36 @@ static void expand_refspecs(void) static void set_refspecs(const char **refs, int nr) { if (nr) { - size_t bytes = nr * sizeof(char *); - - refspec = xrealloc(refspec, bytes); - memcpy(refspec, refs, bytes); - refspec_nr = nr; + int pass; + for (pass = 0; pass < 2; pass++) { + /* pass 0 counts and allocates, pass 1 fills */ + int i, cnt; + for (i = cnt = 0; i < nr; i++) { + if (!strcmp("tag", refs[i])) { + int len; + char *tag; + if (nr <= ++i) + die("tag <tag> shorthand without <tag>"); + if (pass) { + len = strlen(refs[i]) + 11; + tag = xmalloc(len); + strcpy(tag, "refs/tags/"); + strcat(tag, refs[i]); + refspec[cnt] = tag; + } + cnt++; + continue; + } + if (pass) + refspec[cnt] = refs[i]; + cnt++; + } + if (!pass) { + size_t bytes = cnt * sizeof(char *); + refspec_nr = cnt; + refspec = xrealloc(refspec, bytes); + } + } } expand_refspecs(); } diff --git a/builtin-read-tree.c b/builtin-read-tree.c index c1867d2a00..8ba436dbac 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -10,6 +10,7 @@ #include "tree-walk.h" #include "cache-tree.h" #include "unpack-trees.h" +#include "dir.h" #include "builtin.h" static struct object_list *trees; @@ -84,7 +85,7 @@ static void prime_cache_tree(void) } -static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] <sha1> [<sha2> [<sha3>]])"; +static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] <sha1> [<sha2> [<sha3>]])"; static struct lock_file lock_file; @@ -178,6 +179,23 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) continue; } + if (!strncmp(arg, "--exclude-per-directory=", 24)) { + struct dir_struct *dir; + + if (opts.dir) + die("more than one --exclude-per-directory are given."); + + dir = calloc(1, sizeof(*opts.dir)); + dir->show_ignored = 1; + dir->exclude_per_dir = arg + 24; + opts.dir = dir; + /* We do not need to nor want to do read-directory + * here; we are merely interested in reusing the + * per directory ignore stack mechanism. + */ + continue; + } + /* using -u and -i at the same time makes no sense */ if (1 < opts.index_only + opts.update) usage(read_tree_usage); @@ -190,6 +208,8 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) } if ((opts.update||opts.index_only) && !opts.merge) usage(read_tree_usage); + if ((opts.dir && !opts.update)) + die("--exclude-per-directory is meaningless unless -u"); if (opts.prefix) { int pfxlen = strlen(opts.prefix); diff --git a/builtin-reflog.c b/builtin-reflog.c new file mode 100644 index 0000000000..de31967b99 --- /dev/null +++ b/builtin-reflog.c @@ -0,0 +1,212 @@ +#include "cache.h" +#include "builtin.h" +#include "commit.h" +#include "refs.h" +#include "dir.h" +#include "tree-walk.h" + +struct expire_reflog_cb { + FILE *newlog; + const char *ref; + struct commit *ref_commit; + unsigned long expire_total; + unsigned long expire_unreachable; +}; + +static int tree_is_complete(const unsigned char *sha1) +{ + struct tree_desc desc; + void *buf; + char type[20]; + + buf = read_sha1_file(sha1, type, &desc.size); + if (!buf) + return 0; + desc.buf = buf; + while (desc.size) { + const unsigned char *elem; + const char *name; + unsigned mode; + + elem = tree_entry_extract(&desc, &name, &mode); + if (!has_sha1_file(elem) || + (S_ISDIR(mode) && !tree_is_complete(elem))) { + free(buf); + return 0; + } + update_tree_entry(&desc); + } + free(buf); + return 1; +} + +static int keep_entry(struct commit **it, unsigned char *sha1) +{ + struct commit *commit; + + *it = NULL; + if (is_null_sha1(sha1)) + return 1; + commit = lookup_commit_reference_gently(sha1, 1); + if (!commit) + return 0; + + /* Make sure everything in this commit exists. */ + parse_object(commit->object.sha1); + if (!tree_is_complete(commit->tree->object.sha1)) + return 0; + *it = commit; + return 1; +} + +static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, + char *data, void *cb_data) +{ + struct expire_reflog_cb *cb = cb_data; + unsigned long timestamp; + char *cp, *ep; + struct commit *old, *new; + + cp = strchr(data, '>'); + if (!cp || *++cp != ' ') + goto prune; + timestamp = strtoul(cp, &ep, 10); + if (*ep != ' ') + goto prune; + if (timestamp < cb->expire_total) + goto prune; + + if (!keep_entry(&old, osha1) || !keep_entry(&new, nsha1)) + goto prune; + + if ((timestamp < cb->expire_unreachable) && + (!cb->ref_commit || + (old && !in_merge_bases(old, cb->ref_commit)) || + (new && !in_merge_bases(new, cb->ref_commit)))) + goto prune; + + if (cb->newlog) + fprintf(cb->newlog, "%s %s %s", + sha1_to_hex(osha1), sha1_to_hex(nsha1), data); + return 0; + prune: + if (!cb->newlog) + fprintf(stderr, "would prune %s", data); + return 0; +} + +struct cmd_reflog_expire_cb { + int dry_run; + unsigned long expire_total; + unsigned long expire_unreachable; +}; + +static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data) +{ + struct cmd_reflog_expire_cb *cmd = cb_data; + struct expire_reflog_cb cb; + struct ref_lock *lock; + char *newlog_path = NULL; + int status = 0; + + if (strncmp(ref, "refs/", 5)) + return error("not a ref '%s'", ref); + + memset(&cb, 0, sizeof(cb)); + /* we take the lock for the ref itself to prevent it from + * getting updated. + */ + lock = lock_ref_sha1(ref + 5, sha1); + if (!lock) + return error("cannot lock ref '%s'", ref); + if (!file_exists(lock->log_file)) + goto finish; + if (!cmd->dry_run) { + newlog_path = xstrdup(git_path("logs/%s.lock", ref)); + cb.newlog = fopen(newlog_path, "w"); + } + + cb.ref_commit = lookup_commit_reference_gently(sha1, 1); + if (!cb.ref_commit) + fprintf(stderr, + "warning: ref '%s' does not point at a commit\n", ref); + cb.ref = ref; + cb.expire_total = cmd->expire_total; + cb.expire_unreachable = cmd->expire_unreachable; + for_each_reflog_ent(ref, expire_reflog_ent, &cb); + finish: + if (cb.newlog) { + if (fclose(cb.newlog)) + status |= error("%s: %s", strerror(errno), + newlog_path); + if (rename(newlog_path, lock->log_file)) { + status |= error("cannot rename %s to %s", + newlog_path, lock->log_file); + unlink(newlog_path); + } + } + free(newlog_path); + unlock_ref(lock); + return status; +} + +static const char reflog_expire_usage[] = +"git-reflog expire [--dry-run] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>..."; + +static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) +{ + struct cmd_reflog_expire_cb cb; + unsigned long now = time(NULL); + int i, status, do_all; + + save_commit_buffer = 0; + do_all = status = 0; + memset(&cb, 0, sizeof(cb)); + cb.expire_total = now - 90 * 24 * 3600; + cb.expire_unreachable = now - 30 * 24 * 3600; + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n")) + cb.dry_run = 1; + else if (!strncmp(arg, "--expire=", 9)) + cb.expire_total = approxidate(arg + 9); + else if (!strncmp(arg, "--expire-unreachable=", 21)) + cb.expire_unreachable = approxidate(arg + 21); + else if (!strcmp(arg, "--all")) + do_all = 1; + else if (!strcmp(arg, "--")) { + i++; + break; + } + else if (arg[0] == '-') + usage(reflog_expire_usage); + else + break; + } + if (do_all) + status |= for_each_ref(expire_reflog, &cb); + while (i < argc) { + const char *ref = argv[i++]; + unsigned char sha1[20]; + if (!resolve_ref(ref, sha1, 1, NULL)) { + status |= error("%s points nowhere!", ref); + continue; + } + status |= expire_reflog(ref, sha1, 0, &cb); + } + return status; +} + +static const char reflog_usage[] = +"git-reflog (expire | ...)"; + +int cmd_reflog(int argc, const char **argv, const char *prefix) +{ + if (argc < 2) + usage(reflog_usage); + else if (!strcmp(argv[1], "expire")) + return cmd_reflog_expire(argc - 1, argv + 1, prefix); + else + usage(reflog_usage); +} diff --git a/builtin-repo-config.c b/builtin-repo-config.c index 7b6e5725ae..90633119d4 100644 --- a/builtin-repo-config.c +++ b/builtin-repo-config.c @@ -1,9 +1,8 @@ #include "builtin.h" #include "cache.h" -#include <regex.h> static const char git_config_set_usage[] = -"git-repo-config [ --global ] [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list"; +"git-repo-config [ --global ] [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --list"; static char *key; static regex_t *key_regexp; @@ -67,10 +66,10 @@ static int get_value(const char* key_, const char* regex_) char *global = NULL, *repo_config = NULL; const char *local; - local = getenv("GIT_CONFIG"); + local = getenv(CONFIG_ENVIRONMENT); if (!local) { const char *home = getenv("HOME"); - local = getenv("GIT_CONFIG_LOCAL"); + local = getenv(CONFIG_LOCAL_ENVIRONMENT); if (!local) local = repo_config = xstrdup(git_path("config")); if (home) @@ -148,6 +147,18 @@ int cmd_repo_config(int argc, const char **argv, const char *prefix) } else { die("$HOME not set"); } + } else if (!strcmp(argv[1], "--rename-section")) { + int ret; + if (argc != 4) + usage(git_config_set_usage); + ret = git_config_rename_section(argv[2], argv[3]); + if (ret < 0) + return ret; + if (ret == 0) { + fprintf(stderr, "No such section!\n"); + return 1; + } + return 0; } else break; argc--; @@ -190,7 +201,9 @@ int cmd_repo_config(int argc, const char **argv, const char *prefix) use_key_regexp = 1; do_all = 1; return get_value(argv[2], argv[3]); - } else if (!strcmp(argv[1], "--replace-all")) + } else if (!strcmp(argv[1], "--add")) + return git_config_set_multivar(argv[2], argv[3], "^$", 0); + else if (!strcmp(argv[1], "--replace-all")) return git_config_set_multivar(argv[2], argv[3], NULL, 1); else diff --git a/builtin-rerere.c b/builtin-rerere.c new file mode 100644 index 0000000000..d064bd8bf0 --- /dev/null +++ b/builtin-rerere.c @@ -0,0 +1,406 @@ +#include "cache.h" +#include "path-list.h" +#include "xdiff/xdiff.h" +#include "xdiff-interface.h" + +#include <time.h> + +static const char git_rerere_usage[] = +"git-rerere [clear | status | diff | gc]"; + +/* these values are days */ +static int cutoff_noresolve = 15; +static int cutoff_resolve = 60; + +static char *merge_rr_path; + +static const char *rr_path(const char *name, const char *file) +{ + return git_path("rr-cache/%s/%s", name, file); +} + +static void read_rr(struct path_list *rr) +{ + unsigned char sha1[20]; + char buf[PATH_MAX]; + FILE *in = fopen(merge_rr_path, "r"); + if (!in) + return; + while (fread(buf, 40, 1, in) == 1) { + int i; + char *name; + if (get_sha1_hex(buf, sha1)) + die("corrupt MERGE_RR"); + buf[40] = '\0'; + name = xstrdup(buf); + if (fgetc(in) != '\t') + die("corrupt MERGE_RR"); + for (i = 0; i < sizeof(buf) && (buf[i] = fgetc(in)); i++) + ; /* do nothing */ + if (i == sizeof(buf)) + die("filename too long"); + path_list_insert(buf, rr)->util = xstrdup(name); + } + fclose(in); +} + +static struct lock_file write_lock; + +static int write_rr(struct path_list *rr, int out_fd) +{ + int i; + for (i = 0; i < rr->nr; i++) { + const char *path = rr->items[i].path; + write(out_fd, rr->items[i].util, 40); + write(out_fd, "\t", 1); + write(out_fd, path, strlen(path) + 1); + } + close(out_fd); + return commit_lock_file(&write_lock); +} + +struct buffer { + char *ptr; + int nr, alloc; +}; + +static void append_line(struct buffer *buffer, const char *line) +{ + int len = strlen(line); + + if (buffer->nr + len > buffer->alloc) { + buffer->alloc = alloc_nr(buffer->nr + len); + buffer->ptr = xrealloc(buffer->ptr, buffer->alloc); + } + memcpy(buffer->ptr + buffer->nr, line, len); + buffer->nr += len; +} + +static int handle_file(const char *path, + unsigned char *sha1, const char *output) +{ + SHA_CTX ctx; + char buf[1024]; + int hunk = 0, hunk_no = 0; + struct buffer minus = { NULL, 0, 0 }, plus = { NULL, 0, 0 }; + struct buffer *one = &minus, *two = + + FILE *f = fopen(path, "r"); + FILE *out; + + if (!f) + return error("Could not open %s", path); + + if (output) { + out = fopen(output, "w"); + if (!out) { + fclose(f); + return error("Could not write %s", output); + } + } else + out = NULL; + + if (sha1) + SHA1_Init(&ctx); + + while (fgets(buf, sizeof(buf), f)) { + if (!strncmp("<<<<<<< ", buf, 8)) + hunk = 1; + else if (!strncmp("=======", buf, 7)) + hunk = 2; + else if (!strncmp(">>>>>>> ", buf, 8)) { + hunk_no++; + hunk = 0; + if (memcmp(one->ptr, two->ptr, one->nr < two->nr ? + one->nr : two->nr) > 0) { + struct buffer *swap = one; + one = two; + two = swap; + } + if (out) { + fputs("<<<<<<<\n", out); + fwrite(one->ptr, one->nr, 1, out); + fputs("=======\n", out); + fwrite(two->ptr, two->nr, 1, out); + fputs(">>>>>>>\n", out); + } + if (sha1) { + SHA1_Update(&ctx, one->ptr, one->nr); + SHA1_Update(&ctx, "\0", 1); + SHA1_Update(&ctx, two->ptr, two->nr); + SHA1_Update(&ctx, "\0", 1); + } + } else if (hunk == 1) + append_line(one, buf); + else if (hunk == 2) + append_line(two, buf); + else if (out) + fputs(buf, out); + } + + fclose(f); + if (out) + fclose(out); + if (sha1) + SHA1_Final(sha1, &ctx); + return hunk_no; +} + +static int find_conflict(struct path_list *conflict) +{ + int i; + if (read_cache() < 0) + return error("Could not read index"); + for (i = 0; i + 2 < active_nr; i++) { + struct cache_entry *e1 = active_cache[i]; + struct cache_entry *e2 = active_cache[i + 1]; + struct cache_entry *e3 = active_cache[i + 2]; + if (ce_stage(e1) == 1 && ce_stage(e2) == 2 && + ce_stage(e3) == 3 && ce_same_name(e1, e2) && + ce_same_name(e1, e3)) { + path_list_insert((const char *)e1->name, conflict); + i += 3; + } + } + return 0; +} + +static int merge(const char *name, const char *path) +{ + int ret; + mmfile_t cur, base, other; + mmbuffer_t result = {NULL, 0}; + xpparam_t xpp = {XDF_NEED_MINIMAL}; + + if (handle_file(path, NULL, rr_path(name, "thisimage")) < 0) + return 1; + + if (read_mmfile(&cur, rr_path(name, "thisimage")) || + read_mmfile(&base, rr_path(name, "preimage")) || + read_mmfile(&other, rr_path(name, "postimage"))) + return 1; + ret = xdl_merge(&base, &cur, "", &other, "", + &xpp, XDL_MERGE_ZEALOUS, &result); + if (!ret) { + FILE *f = fopen(path, "w"); + if (!f) + return error("Could not write to %s", path); + fwrite(result.ptr, result.size, 1, f); + fclose(f); + } + + free(cur.ptr); + free(base.ptr); + free(other.ptr); + free(result.ptr); + + return ret; +} + +static void unlink_rr_item(const char *name) +{ + unlink(rr_path(name, "thisimage")); + unlink(rr_path(name, "preimage")); + unlink(rr_path(name, "postimage")); + rmdir(git_path("rr-cache/%s", name)); +} + +static void garbage_collect(struct path_list *rr) +{ + struct path_list to_remove = { NULL, 0, 0, 1 }; + char buf[1024]; + DIR *dir; + struct dirent *e; + int len, i, cutoff; + time_t now = time(NULL), then; + + strlcpy(buf, git_path("rr-cache"), sizeof(buf)); + len = strlen(buf); + dir = opendir(buf); + strcpy(buf + len++, "/"); + while ((e = readdir(dir))) { + const char *name = e->d_name; + struct stat st; + if (name[0] == '.' && (name[1] == '\0' || + (name[1] == '.' && name[2] == '\0'))) + continue; + i = snprintf(buf + len, sizeof(buf) - len, "%s", name); + strlcpy(buf + len + i, "/preimage", sizeof(buf) - len - i); + if (stat(buf, &st)) + continue; + then = st.st_mtime; + strlcpy(buf + len + i, "/postimage", sizeof(buf) - len - i); + cutoff = stat(buf, &st) ? cutoff_noresolve : cutoff_resolve; + if (then < now - cutoff * 86400) { + buf[len + i] = '\0'; + path_list_insert(xstrdup(name), &to_remove); + } + } + for (i = 0; i < to_remove.nr; i++) + unlink_rr_item(to_remove.items[i].path); + path_list_clear(&to_remove, 0); +} + +static int outf(void *dummy, mmbuffer_t *ptr, int nbuf) +{ + int i; + for (i = 0; i < nbuf; i++) + write(1, ptr[i].ptr, ptr[i].size); + return 0; +} + +static int diff_two(const char *file1, const char *label1, + const char *file2, const char *label2) +{ + xpparam_t xpp; + xdemitconf_t xecfg; + xdemitcb_t ecb; + mmfile_t minus, plus; + + if (read_mmfile(&minus, file1) || read_mmfile(&plus, file2)) + return 1; + + printf("--- a/%s\n+++ b/%s\n", label1, label2); + fflush(stdout); + xpp.flags = XDF_NEED_MINIMAL; + xecfg.ctxlen = 3; + xecfg.flags = 0; + ecb.outf = outf; + xdl_diff(&minus, &plus, &xpp, &xecfg, &ecb); + + free(minus.ptr); + free(plus.ptr); + return 0; +} + +static int copy_file(const char *src, const char *dest) +{ + FILE *in, *out; + char buffer[32768]; + int count; + + if (!(in = fopen(src, "r"))) + return error("Could not open %s", src); + if (!(out = fopen(dest, "w"))) + return error("Could not open %s", dest); + while ((count = fread(buffer, 1, sizeof(buffer), in))) + fwrite(buffer, 1, count, out); + fclose(in); + fclose(out); + return 0; +} + +static int do_plain_rerere(struct path_list *rr, int fd) +{ + struct path_list conflict = { NULL, 0, 0, 1 }; + int i; + + find_conflict(&conflict); + + /* + * MERGE_RR records paths with conflicts immediately after merge + * failed. Some of the conflicted paths might have been hand resolved + * in the working tree since then, but the initial run would catch all + * and register their preimages. + */ + + for (i = 0; i < conflict.nr; i++) { + const char *path = conflict.items[i].path; + if (!path_list_has_path(rr, path)) { + unsigned char sha1[20]; + char *hex; + int ret; + ret = handle_file(path, sha1, NULL); + if (ret < 1) + continue; + hex = xstrdup(sha1_to_hex(sha1)); + path_list_insert(path, rr)->util = hex; + if (mkdir(git_path("rr-cache/%s", hex), 0755)) + continue;; + handle_file(path, NULL, rr_path(hex, "preimage")); + fprintf(stderr, "Recorded preimage for '%s'\n", path); + } + } + + /* + * Now some of the paths that had conflicts earlier might have been + * hand resolved. Others may be similar to a conflict already that + * was resolved before. + */ + + for (i = 0; i < rr->nr; i++) { + struct stat st; + int ret; + const char *path = rr->items[i].path; + const char *name = (const char *)rr->items[i].util; + + if (!stat(rr_path(name, "preimage"), &st) && + !stat(rr_path(name, "postimage"), &st)) { + if (!merge(name, path)) { + fprintf(stderr, "Resolved '%s' using " + "previous resolution.\n", path); + goto tail_optimization; + } + } + + /* Let's see if we have resolved it. */ + ret = handle_file(path, NULL, NULL); + if (ret) + continue; + + fprintf(stderr, "Recorded resolution for '%s'.\n", path); + copy_file(path, rr_path(name, "postimage")); +tail_optimization: + if (i < rr->nr - 1) { + memmove(rr->items + i, + rr->items + i + 1, + rr->nr - i - 1); + } + rr->nr--; + i--; + } + + return write_rr(rr, fd); +} + +int cmd_rerere(int argc, const char **argv, const char *prefix) +{ + struct path_list merge_rr = { NULL, 0, 0, 1 }; + int i, fd = -1; + struct stat st; + + if (stat(git_path("rr-cache"), &st) || !S_ISDIR(st.st_mode)) + return 0; + + merge_rr_path = xstrdup(git_path("rr-cache/MERGE_RR")); + fd = hold_lock_file_for_update(&write_lock, merge_rr_path, 1); + read_rr(&merge_rr); + + if (argc < 2) + return do_plain_rerere(&merge_rr, fd); + else if (!strcmp(argv[1], "clear")) { + for (i = 0; i < merge_rr.nr; i++) { + const char *name = (const char *)merge_rr.items[i].util; + if (!stat(git_path("rr-cache/%s", name), &st) && + S_ISDIR(st.st_mode) && + stat(rr_path(name, "postimage"), &st)) + unlink_rr_item(name); + } + unlink(merge_rr_path); + } else if (!strcmp(argv[1], "gc")) + garbage_collect(&merge_rr); + else if (!strcmp(argv[1], "status")) + for (i = 0; i < merge_rr.nr; i++) + printf("%s\n", merge_rr.items[i].path); + else if (!strcmp(argv[1], "diff")) + for (i = 0; i < merge_rr.nr; i++) { + const char *path = merge_rr.items[i].path; + const char *name = (const char *)merge_rr.items[i].util; + diff_two(rr_path(name, "preimage"), path, path, path); + } + else + usage(git_rerere_usage); + + path_list_clear(&merge_rr, 1); + return 0; +} + diff --git a/builtin-rev-list.c b/builtin-rev-list.c index fb7fc92145..1bb3a06680 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -54,6 +54,12 @@ static void show_commit(struct commit *commit) fputs(header_prefix, stdout); if (commit->object.flags & BOUNDARY) putchar('-'); + else if (revs.left_right) { + if (commit->object.flags & SYMMETRIC_LEFT) + putchar('<'); + else + putchar('>'); + } if (revs.abbrev_commit && revs.abbrev) fputs(find_unique_abbrev(commit->object.sha1, revs.abbrev), stdout); diff --git a/builtin-rm.c b/builtin-rm.c index 33d04bd015..5b078c4194 100644 --- a/builtin-rm.c +++ b/builtin-rm.c @@ -7,9 +7,10 @@ #include "builtin.h" #include "dir.h" #include "cache-tree.h" +#include "tree-walk.h" static const char builtin_rm_usage[] = -"git-rm [-n] [-v] [-f] <filepattern>..."; +"git-rm [-n] [-f] [--cached] <filepattern>..."; static struct { int nr, alloc; @@ -41,12 +42,75 @@ static int remove_file(const char *name) return ret; } +static int check_local_mod(unsigned char *head) +{ + /* items in list are already sorted in the cache order, + * so we could do this a lot more efficiently by using + * tree_desc based traversal if we wanted to, but I am + * lazy, and who cares if removal of files is a tad + * slower than the theoretical maximum speed? + */ + int i, no_head; + int errs = 0; + + no_head = is_null_sha1(head); + for (i = 0; i < list.nr; i++) { + struct stat st; + int pos; + struct cache_entry *ce; + const char *name = list.name[i]; + unsigned char sha1[20]; + unsigned mode; + + pos = cache_name_pos(name, strlen(name)); + if (pos < 0) + continue; /* removing unmerged entry */ + ce = active_cache[pos]; + + if (lstat(ce->name, &st) < 0) { + if (errno != ENOENT) + fprintf(stderr, "warning: '%s': %s", + ce->name, strerror(errno)); + /* It already vanished from the working tree */ + continue; + } + else if (S_ISDIR(st.st_mode)) { + /* if a file was removed and it is now a + * directory, that is the same as ENOENT as + * far as git is concerned; we do not track + * directories. + */ + continue; + } + if (ce_match_stat(ce, &st, 0)) + errs = error("'%s' has local modifications " + "(hint: try -f)", ce->name); + if (no_head) + continue; + /* + * It is Ok to remove a newly added path, as long as + * it is cache-clean. + */ + if (get_tree_entry(head, name, sha1, &mode)) + continue; + /* + * Otherwise make sure the version from the HEAD + * matches the index. + */ + if (ce->ce_mode != create_ce_mode(mode) || + hashcmp(ce->sha1, sha1)) + errs = error("'%s' has changes staged in the index " + "(hint: try -f)", name); + } + return errs; +} + static struct lock_file lock_file; int cmd_rm(int argc, const char **argv, const char *prefix) { int i, newfd; - int verbose = 0, show_only = 0, force = 0; + int show_only = 0, force = 0, index_only = 0, recursive = 0; const char **pathspec; char *seen; @@ -62,23 +126,20 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (*arg != '-') break; - if (!strcmp(arg, "--")) { + else if (!strcmp(arg, "--")) { i++; break; } - if (!strcmp(arg, "-n")) { + else if (!strcmp(arg, "-n")) show_only = 1; - continue; - } - if (!strcmp(arg, "-v")) { - verbose = 1; - continue; - } - if (!strcmp(arg, "-f")) { + else if (!strcmp(arg, "--cached")) + index_only = 1; + else if (!strcmp(arg, "-f")) force = 1; - continue; - } - usage(builtin_rm_usage); + else if (!strcmp(arg, "-r")) + recursive = 1; + else + usage(builtin_rm_usage); } if (argc <= i) usage(builtin_rm_usage); @@ -99,14 +160,36 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (pathspec) { const char *match; for (i = 0; (match = pathspec[i]) != NULL ; i++) { - if (*match && !seen[i]) - die("pathspec '%s' did not match any files", match); + if (!seen[i]) + die("pathspec '%s' did not match any files", + match); + if (!recursive && seen[i] == MATCHED_RECURSIVELY) + die("not removing '%s' recursively without -r", + *match ? match : "."); } } /* + * If not forced, the file, the index and the HEAD (if exists) + * must match; but the file can already been removed, since + * this sequence is a natural "novice" way: + * + * rm F; git fm F + * + * Further, if HEAD commit exists, "diff-index --cached" must + * report no changes unless forced. + */ + if (!force) { + unsigned char sha1[20]; + if (get_sha1("HEAD", sha1)) + hashclr(sha1); + if (check_local_mod(sha1)) + exit(1); + } + + /* * First remove the names from the index: we won't commit - * the index unless all of them succeed + * the index unless all of them succeed. */ for (i = 0; i < list.nr; i++) { const char *path = list.name[i]; @@ -121,14 +204,14 @@ int cmd_rm(int argc, const char **argv, const char *prefix) return 0; /* - * Then, if we used "-f", remove the filenames from the - * workspace. If we fail to remove the first one, we + * Then, unless we used "--cache", remove the filenames from + * the workspace. If we fail to remove the first one, we * abort the "git rm" (but once we've successfully removed * any file at all, we'll go ahead and commit to it all: * by then we've already committed ourselves and can't fail * in the middle) */ - if (force) { + if (!index_only) { int removed = 0; for (i = 0; i < list.nr; i++) { const char *path = list.name[i]; diff --git a/builtin-runstatus.c b/builtin-runstatus.c index 0b63037dd0..4b489b1214 100644 --- a/builtin-runstatus.c +++ b/builtin-runstatus.c @@ -1,5 +1,5 @@ -#include "wt-status.h" #include "cache.h" +#include "wt-status.h" extern int wt_status_use_color; diff --git a/builtin-shortlog.c b/builtin-shortlog.c new file mode 100644 index 0000000000..edb40429ec --- /dev/null +++ b/builtin-shortlog.c @@ -0,0 +1,341 @@ +#include "builtin.h" +#include "cache.h" +#include "commit.h" +#include "diff.h" +#include "path-list.h" +#include "revision.h" + +static const char shortlog_usage[] = +"git-shortlog [-n] [-s] [<commit-id>... ]"; + +static char *common_repo_prefix; + +static int compare_by_number(const void *a1, const void *a2) +{ + const struct path_list_item *i1 = a1, *i2 = a2; + const struct path_list *l1 = i1->util, *l2 = i2->util; + + if (l1->nr < l2->nr) + return 1; + else if (l1->nr == l2->nr) + return 0; + else + return -1; +} + +static struct path_list mailmap = {NULL, 0, 0, 0}; + +static int read_mailmap(const char *filename) +{ + char buffer[1024]; + FILE *f = fopen(filename, "r"); + + if (f == NULL) + return 1; + while (fgets(buffer, sizeof(buffer), f) != NULL) { + char *end_of_name, *left_bracket, *right_bracket; + char *name, *email; + int i; + if (buffer[0] == '#') { + static const char abbrev[] = "# repo-abbrev:"; + int abblen = sizeof(abbrev) - 1; + int len = strlen(buffer); + + if (len && buffer[len - 1] == '\n') + buffer[--len] = 0; + if (!strncmp(buffer, abbrev, abblen)) { + char *cp; + + if (common_repo_prefix) + free(common_repo_prefix); + common_repo_prefix = xmalloc(len); + + for (cp = buffer + abblen; isspace(*cp); cp++) + ; /* nothing */ + strcpy(common_repo_prefix, cp); + } + continue; + } + if ((left_bracket = strchr(buffer, '<')) == NULL) + continue; + if ((right_bracket = strchr(left_bracket + 1, '>')) == NULL) + continue; + if (right_bracket == left_bracket + 1) + continue; + for (end_of_name = left_bracket; end_of_name != buffer + && isspace(end_of_name[-1]); end_of_name--) + /* keep on looking */ + if (end_of_name == buffer) + continue; + name = xmalloc(end_of_name - buffer + 1); + strlcpy(name, buffer, end_of_name - buffer + 1); + email = xmalloc(right_bracket - left_bracket); + for (i = 0; i < right_bracket - left_bracket - 1; i++) + email[i] = tolower(left_bracket[i + 1]); + email[right_bracket - left_bracket - 1] = '\0'; + path_list_insert(email, &mailmap)->util = name; + } + fclose(f); + return 0; +} + +static int map_email(char *email, char *name, int maxlen) +{ + char *p; + struct path_list_item *item; + + /* autocomplete common developers */ + p = strchr(email, '>'); + if (!p) + return 0; + + *p = '\0'; + /* downcase the email address */ + for (p = email; *p; p++) + *p = tolower(*p); + item = path_list_lookup(email, &mailmap); + if (item != NULL) { + const char *realname = (const char *)item->util; + strncpy(name, realname, maxlen); + return 1; + } + return 0; +} + +static void insert_author_oneline(struct path_list *list, + const char *author, int authorlen, + const char *oneline, int onelinelen) +{ + const char *dot3 = common_repo_prefix; + char *buffer, *p; + struct path_list_item *item; + struct path_list *onelines; + + while (authorlen > 0 && isspace(author[authorlen - 1])) + authorlen--; + + buffer = xmalloc(authorlen + 1); + memcpy(buffer, author, authorlen); + buffer[authorlen] = '\0'; + + item = path_list_insert(buffer, list); + if (item->util == NULL) + item->util = xcalloc(1, sizeof(struct path_list)); + else + free(buffer); + + if (!strncmp(oneline, "[PATCH", 6)) { + char *eob = strchr(oneline, ']'); + + if (eob) { + while (isspace(eob[1]) && eob[1] != '\n') + eob++; + if (eob - oneline < onelinelen) { + onelinelen -= eob - oneline; + oneline = eob; + } + } + } + + while (onelinelen > 0 && isspace(oneline[0])) { + oneline++; + onelinelen--; + } + + while (onelinelen > 0 && isspace(oneline[onelinelen - 1])) + onelinelen--; + + buffer = xmalloc(onelinelen + 1); + memcpy(buffer, oneline, onelinelen); + buffer[onelinelen] = '\0'; + + if (dot3) { + int dot3len = strlen(dot3); + if (dot3len > 5) { + while ((p = strstr(buffer, dot3)) != NULL) { + int taillen = strlen(p) - dot3len; + memcpy(p, "/.../", 5); + memmove(p + 5, p + dot3len, taillen + 1); + } + } + } + + onelines = item->util; + if (onelines->nr >= onelines->alloc) { + onelines->alloc = alloc_nr(onelines->nr); + onelines->items = xrealloc(onelines->items, + onelines->alloc + * sizeof(struct path_list_item)); + } + + onelines->items[onelines->nr].util = NULL; + onelines->items[onelines->nr++].path = buffer; +} + +static void read_from_stdin(struct path_list *list) +{ + char buffer[1024]; + + while (fgets(buffer, sizeof(buffer), stdin) != NULL) { + char *bob; + if ((buffer[0] == 'A' || buffer[0] == 'a') && + !strncmp(buffer + 1, "uthor: ", 7) && + (bob = strchr(buffer + 7, '<')) != NULL) { + char buffer2[1024], offset = 0; + + if (map_email(bob + 1, buffer, sizeof(buffer))) + bob = buffer + strlen(buffer); + else { + offset = 8; + while (buffer + offset < bob && + isspace(bob[-1])) + bob--; + } + + while (fgets(buffer2, sizeof(buffer2), stdin) && + buffer2[0] != '\n') + ; /* chomp input */ + if (fgets(buffer2, sizeof(buffer2), stdin)) { + int l2 = strlen(buffer2); + int i; + for (i = 0; i < l2; i++) + if (!isspace(buffer2[i])) + break; + insert_author_oneline(list, + buffer + offset, + bob - buffer - offset, + buffer2 + i, l2 - i); + } + } + } +} + +static void get_from_rev(struct rev_info *rev, struct path_list *list) +{ + char scratch[1024]; + struct commit *commit; + + prepare_revision_walk(rev); + while ((commit = get_revision(rev)) != NULL) { + char *author = NULL, *oneline, *buffer; + int authorlen = authorlen, onelinelen; + + /* get author and oneline */ + for (buffer = commit->buffer; buffer && *buffer != '\0' && + *buffer != '\n'; ) { + char *eol = strchr(buffer, '\n'); + + if (eol == NULL) + eol = buffer + strlen(buffer); + else + eol++; + + if (!strncmp(buffer, "author ", 7)) { + char *bracket = strchr(buffer, '<'); + + if (bracket == NULL || bracket > eol) + die("Invalid commit buffer: %s", + sha1_to_hex(commit->object.sha1)); + + if (map_email(bracket + 1, scratch, + sizeof(scratch))) { + author = scratch; + authorlen = strlen(scratch); + } else { + if (bracket[-1] == ' ') + bracket--; + + author = buffer + 7; + authorlen = bracket - buffer - 7; + } + } + buffer = eol; + } + + if (author == NULL) + die ("Missing author: %s", + sha1_to_hex(commit->object.sha1)); + + if (buffer == NULL || *buffer == '\0') { + oneline = "<none>"; + onelinelen = sizeof(oneline) + 1; + } else { + char *eol; + + oneline = buffer + 1; + eol = strchr(oneline, '\n'); + if (eol == NULL) + onelinelen = strlen(oneline); + else + onelinelen = eol - oneline; + } + + insert_author_oneline(list, + author, authorlen, oneline, onelinelen); + } + +} + +int cmd_shortlog(int argc, const char **argv, const char *prefix) +{ + struct rev_info rev; + struct path_list list = { NULL, 0, 0, 1 }; + int i, j, sort_by_number = 0, summary = 0; + + /* since -n is a shadowed rev argument, parse our args first */ + while (argc > 1) { + if (!strcmp(argv[1], "-n") || !strcmp(argv[1], "--numbered")) + sort_by_number = 1; + else if (!strcmp(argv[1], "-s") || + !strcmp(argv[1], "--summary")) + summary = 1; + else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) + usage(shortlog_usage); + else + break; + argv++; + argc--; + } + init_revisions(&rev, prefix); + argc = setup_revisions(argc, argv, &rev, NULL); + if (argc > 1) + die ("unrecognized argument: %s", argv[1]); + + if (!access(".mailmap", R_OK)) + read_mailmap(".mailmap"); + + if (rev.pending.nr == 0) + read_from_stdin(&list); + else + get_from_rev(&rev, &list); + + if (sort_by_number) + qsort(list.items, list.nr, sizeof(struct path_list_item), + compare_by_number); + + for (i = 0; i < list.nr; i++) { + struct path_list *onelines = list.items[i].util; + + if (summary) { + printf("%s: %d\n", list.items[i].path, onelines->nr); + } else { + printf("%s (%d):\n", list.items[i].path, onelines->nr); + for (j = onelines->nr - 1; j >= 0; j--) + printf(" %s\n", onelines->items[j].path); + printf("\n"); + } + + onelines->strdup_paths = 1; + path_list_clear(onelines, 1); + free(onelines); + list.items[i].util = NULL; + } + + list.strdup_paths = 1; + path_list_clear(&list, 1); + mailmap.strdup_paths = 1; + path_list_clear(&mailmap, 1); + + return 0; +} + diff --git a/builtin-show-branch.c b/builtin-show-branch.c index fb1a4000d9..c67f2fa2fe 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -1,12 +1,10 @@ -#include <stdlib.h> -#include <fnmatch.h> #include "cache.h" #include "commit.h" #include "refs.h" #include "builtin.h" static const char show_branch_usage[] = -"git-show-branch [--sparse] [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]"; +"git-show-branch [--sparse] [--current] [--all] [--remotes] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...] | --reflog[=n] <branch>"; static int default_num; static int default_alloc; @@ -17,6 +15,8 @@ static const char **default_arg; #define REV_SHIFT 2 #define MAX_REVS (FLAG_BITS - REV_SHIFT) /* should not exceed bits_per_int - REV_SHIFT */ +#define DEFAULT_REFLOG 4 + static struct commit *interesting(struct commit_list *list) { while (list) { @@ -383,6 +383,20 @@ static int append_head_ref(const char *refname, const unsigned char *sha1, int f return append_ref(refname + ofs, sha1, flag, cb_data); } +static int append_remote_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) +{ + unsigned char tmp[20]; + int ofs = 13; + if (strncmp(refname, "refs/remotes/", ofs)) + return 0; + /* If both heads/foo and tags/foo exists, get_sha1 would + * get confused. + */ + if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1)) + ofs = 5; + return append_ref(refname + ofs, sha1, flag, cb_data); +} + static int append_tag_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { if (strncmp(refname, "refs/tags/", 10)) @@ -423,16 +437,16 @@ static int append_matching_ref(const char *refname, const unsigned char *sha1, i return append_ref(refname, sha1, flag, cb_data); } -static void snarf_refs(int head, int tag) +static void snarf_refs(int head, int remotes) { if (head) { int orig_cnt = ref_name_cnt; for_each_ref(append_head_ref, NULL); sort_ref_range(orig_cnt, ref_name_cnt); } - if (tag) { + if (remotes) { int orig_cnt = ref_name_cnt; - for_each_ref(append_tag_ref, NULL); + for_each_ref(append_remote_ref, NULL); sort_ref_range(orig_cnt, ref_name_cnt); } } @@ -554,7 +568,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) struct commit_list *list = NULL, *seen = NULL; unsigned int rev_mask[MAX_REVS]; int num_rev, i, extra = 0; - int all_heads = 0, all_tags = 0; + int all_heads = 0, all_remotes = 0; int all_mask, all_revs; int lifo = 1; char head[128]; @@ -570,6 +584,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) int head_at = -1; int topics = 0; int dense = 1; + int reflog = 0; git_config(git_show_branch_config); @@ -585,12 +600,10 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) ac--; av++; break; } - else if (!strcmp(arg, "--all")) - all_heads = all_tags = 1; - else if (!strcmp(arg, "--heads")) - all_heads = 1; - else if (!strcmp(arg, "--tags")) - all_tags = 1; + else if (!strcmp(arg, "--all") || !strcmp(arg, "-a")) + all_heads = all_remotes = 1; + else if (!strcmp(arg, "--remotes") || !strcmp(arg, "-r")) + all_remotes = 1; else if (!strcmp(arg, "--more")) extra = 1; else if (!strcmp(arg, "--list")) @@ -615,6 +628,15 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) dense = 0; else if (!strcmp(arg, "--date-order")) lifo = 0; + else if (!strcmp(arg, "--reflog")) { + reflog = DEFAULT_REFLOG; + } + else if (!strncmp(arg, "--reflog=", 9)) { + char *end; + reflog = strtoul(arg + 9, &end, 10); + if (*end != '\0') + die("unrecognized reflog count '%s'", arg + 9); + } else usage(show_branch_usage); ac--; av++; @@ -622,18 +644,31 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) ac--; av++; /* Only one of these is allowed */ - if (1 < independent + merge_base + (extra != 0)) + if (1 < independent + merge_base + (extra != 0) + (!!reflog)) usage(show_branch_usage); /* If nothing is specified, show all branches by default */ - if (ac + all_heads + all_tags == 0) + if (ac + all_heads + all_remotes == 0) all_heads = 1; - if (all_heads + all_tags) - snarf_refs(all_heads, all_tags); - while (0 < ac) { - append_one_rev(*av); - ac--; av++; + if (all_heads + all_remotes) + snarf_refs(all_heads, all_remotes); + if (reflog) { + int reflen; + if (!ac) + die("--reflog option needs one branch name"); + reflen = strlen(*av); + for (i = 0; i < reflog; i++) { + char *name = xmalloc(reflen + 20); + sprintf(name, "%s@{%d}", *av, i); + append_one_rev(name); + } + } + else { + while (0 < ac) { + append_one_rev(*av); + ac--; av++; + } } head_p = resolve_ref("HEAD", head_sha1, 1, NULL); diff --git a/builtin-show-ref.c b/builtin-show-ref.c index 06ec400d7f..853f13f6ae 100644 --- a/builtin-show-ref.c +++ b/builtin-show-ref.c @@ -2,17 +2,28 @@ #include "refs.h" #include "object.h" #include "tag.h" +#include "path-list.h" -static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash[=<length>]] [--abbrev[=<length>]] [--tags] [--heads] [--] [pattern*]"; +static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash[=<length>]] [--abbrev[=<length>]] [--tags] [--heads] [--] [pattern*] < ref-list"; static int deref_tags = 0, show_head = 0, tags_only = 0, heads_only = 0, found_match = 0, verify = 0, quiet = 0, hash_only = 0, abbrev = 0; static const char **pattern; +static void show_one(const char *refname, const unsigned char *sha1) +{ + const char *hex = find_unique_abbrev(sha1, abbrev); + if (hash_only) + printf("%s\n", hex); + else + printf("%s %s\n", hex, refname); +} + static int show_ref(const char *refname, const unsigned char *sha1, int flag, void *cbdata) { struct object *obj; const char *hex; + unsigned char peeled[20]; if (tags_only || heads_only) { int match; @@ -44,24 +55,93 @@ static int show_ref(const char *refname, const unsigned char *sha1, int flag, vo match: found_match++; - obj = parse_object(sha1); - if (!obj) { - if (quiet) - return 0; - die("git-show-ref: bad ref %s (%s)", refname, sha1_to_hex(sha1)); - } + + /* This changes the semantics slightly that even under quiet we + * detect and return error if the repository is corrupt and + * ref points at a nonexistent object. + */ + if (!has_sha1_file(sha1)) + die("git-show-ref: bad ref %s (%s)", refname, + sha1_to_hex(sha1)); + if (quiet) return 0; - hex = find_unique_abbrev(sha1, abbrev); - if (hash_only) - printf("%s\n", hex); - else - printf("%s %s\n", hex, refname); - if (deref_tags && obj->type == OBJ_TAG) { - obj = deref_tag(obj, refname, 0); - hex = find_unique_abbrev(obj->sha1, abbrev); - printf("%s %s^{}\n", hex, refname); + show_one(refname, sha1); + + if (!deref_tags) + return 0; + + if ((flag & REF_ISPACKED) && !peel_ref(refname, peeled)) { + if (!is_null_sha1(peeled)) { + hex = find_unique_abbrev(peeled, abbrev); + printf("%s %s^{}\n", hex, refname); + } + } + else { + obj = parse_object(sha1); + if (!obj) + die("git-show-ref: bad ref %s (%s)", refname, + sha1_to_hex(sha1)); + if (obj->type == OBJ_TAG) { + obj = deref_tag(obj, refname, 0); + hex = find_unique_abbrev(obj->sha1, abbrev); + printf("%s %s^{}\n", hex, refname); + } + } + return 0; +} + +static int add_existing(const char *refname, const unsigned char *sha1, int flag, void *cbdata) +{ + struct path_list *list = (struct path_list *)cbdata; + path_list_insert(refname, list); + return 0; +} + +/* + * read "^(?:<anything>\s)?<refname>(?:\^\{\})?$" from the standard input, + * and + * (1) strip "^{}" at the end of line if any; + * (2) ignore if match is provided and does not head-match refname; + * (3) warn if refname is not a well-formed refname and skip; + * (4) ignore if refname is a ref that exists in the local repository; + * (5) otherwise output the line. + */ +static int exclude_existing(const char *match) +{ + static struct path_list existing_refs = { NULL, 0, 0, 0 }; + char buf[1024]; + int matchlen = match ? strlen(match) : 0; + + for_each_ref(add_existing, &existing_refs); + while (fgets(buf, sizeof(buf), stdin)) { + char *ref; + int len = strlen(buf); + + if (len > 0 && buf[len - 1] == '\n') + buf[--len] = '\0'; + if (3 <= len && !strcmp(buf + len - 3, "^{}")) { + len -= 3; + buf[len] = '\0'; + } + for (ref = buf + len; buf < ref; ref--) + if (isspace(ref[-1])) + break; + if (match) { + int reflen = buf + len - ref; + if (reflen < matchlen) + continue; + if (strncmp(ref, match, matchlen)) + continue; + } + if (check_ref_format(ref)) { + fprintf(stderr, "warning: ref '%s' ignored\n", ref); + continue; + } + if (!path_list_has_path(&existing_refs, ref)) { + printf("%s\n", buf); + } } return 0; } @@ -101,13 +181,13 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix) if (!strncmp(arg, "--hash=", 7) || (!strncmp(arg, "--abbrev", 8) && (arg[8] == '=' || arg[8] == '\0'))) { - if (arg[3] != 'h' && !arg[8]) + if (arg[2] != 'h' && !arg[8]) /* --abbrev only */ abbrev = DEFAULT_ABBREV; else { /* --hash= or --abbrev= */ char *end; - if (arg[3] == 'h') { + if (arg[2] == 'h') { hash_only = 1; arg += 7; } @@ -133,8 +213,31 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix) heads_only = 1; continue; } + if (!strcmp(arg, "--exclude-existing")) + return exclude_existing(NULL); + if (!strncmp(arg, "--exclude-existing=", 19)) + return exclude_existing(arg + 19); usage(show_ref_usage); } + + if (verify) { + unsigned char sha1[20]; + + while (*pattern) { + if (!strncmp(*pattern, "refs/", 5) && + resolve_ref(*pattern, sha1, 1, NULL)) { + if (!quiet) + show_one(*pattern, sha1); + } + else if (!quiet) + die("'%s' - not a valid ref", *pattern); + else + return 1; + pattern++; + } + return 0; + } + if (show_head) head_ref(show_ref, NULL); for_each_ref(show_ref, NULL); diff --git a/builtin-stripspace.c b/builtin-stripspace.c index 09cc9108cd..f0d4d9e2d1 100644 --- a/builtin-stripspace.c +++ b/builtin-stripspace.c @@ -1,6 +1,3 @@ -#include <stdio.h> -#include <string.h> -#include <ctype.h> #include "builtin.h" /* diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c index 4d4cfec878..11e62fc141 100644 --- a/builtin-tar-tree.c +++ b/builtin-tar-tree.c @@ -1,7 +1,6 @@ /* * Copyright (c) 2005, 2006 Rene Scharfe */ -#include <time.h> #include "cache.h" #include "commit.h" #include "tar.h" diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c index e6d7574844..d351e02649 100644 --- a/builtin-unpack-objects.c +++ b/builtin-unpack-objects.c @@ -8,8 +8,6 @@ #include "tag.h" #include "tree.h" -#include <sys/time.h> - static int dry_run, quiet, recover, has_errors; static const char unpack_usage[] = "git-unpack-objects [-n] [-q] [-r] < pack-file"; diff --git a/builtin-upload-archive.c b/builtin-upload-archive.c index 45c92e163c..e4156f8f48 100644 --- a/builtin-upload-archive.c +++ b/builtin-upload-archive.c @@ -1,9 +1,6 @@ /* * Copyright (c) 2006 Franck Bui-Huu */ -#include <time.h> -#include <sys/wait.h> -#include <sys/poll.h> #include "cache.h" #include "builtin.h" #include "archive.h" @@ -42,6 +42,7 @@ extern int cmd_ls_files(int argc, const char **argv, const char *prefix); extern int cmd_ls_tree(int argc, const char **argv, const char *prefix); extern int cmd_mailinfo(int argc, const char **argv, const char *prefix); extern int cmd_mailsplit(int argc, const char **argv, const char *prefix); +extern int cmd_merge_file(int argc, const char **argv, const char *prefix); extern int cmd_mv(int argc, const char **argv, const char *prefix); extern int cmd_name_rev(int argc, const char **argv, const char *prefix); extern int cmd_pack_objects(int argc, const char **argv, const char *prefix); @@ -50,11 +51,14 @@ extern int cmd_prune(int argc, const char **argv, const char *prefix); extern int cmd_prune_packed(int argc, const char **argv, const char *prefix); extern int cmd_push(int argc, const char **argv, const char *prefix); extern int cmd_read_tree(int argc, const char **argv, const char *prefix); +extern int cmd_reflog(int argc, const char **argv, const char *prefix); extern int cmd_repo_config(int argc, const char **argv, const char *prefix); +extern int cmd_rerere(int argc, const char **argv, const char *prefix); extern int cmd_rev_list(int argc, const char **argv, const char *prefix); extern int cmd_rev_parse(int argc, const char **argv, const char *prefix); extern int cmd_rm(int argc, const char **argv, const char *prefix); extern int cmd_runstatus(int argc, const char **argv, const char *prefix); +extern int cmd_shortlog(int argc, const char **argv, const char *prefix); extern int cmd_show(int argc, const char **argv, const char *prefix); extern int cmd_show_branch(int argc, const char **argv, const char *prefix); extern int cmd_stripspace(int argc, const char **argv, const char *prefix); @@ -122,7 +122,12 @@ extern int cache_errno; #define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY" #define INDEX_ENVIRONMENT "GIT_INDEX_FILE" #define GRAFT_ENVIRONMENT "GIT_GRAFT_FILE" +#define TEMPLATE_DIR_ENVIRONMENT "GIT_TEMPLATE_DIR" +#define CONFIG_ENVIRONMENT "GIT_CONFIG" +#define CONFIG_LOCAL_ENVIRONMENT "GIT_CONFIG_LOCAL" +#define EXEC_PATH_ENVIRONMENT "GIT_EXEC_PATH" +extern int is_bare_git_dir(const char *dir); extern const char *get_git_dir(void); extern char *get_object_directory(void); extern char *get_refs_directory(void); @@ -308,6 +313,7 @@ void datestamp(char *buf, int bufsize); unsigned long approxidate(const char *); extern int setup_ident(void); +extern void ignore_missing_committer_name(); extern const char *git_author_info(int); extern const char *git_committer_info(int); @@ -403,6 +409,7 @@ extern int git_config_int(const char *, const char *); extern int git_config_bool(const char *, const char *); extern int git_config_set(const char *, const char *); extern int git_config_set_multivar(const char *, const char *, const char *, int); +extern int git_config_rename_section(const char *, const char *); extern int check_repository_format_version(const char *var, const char *value); #define MAX_GITNAME (1000) @@ -1,8 +1,5 @@ -#include "color.h" #include "cache.h" -#include "git-compat-util.h" - -#include <stdarg.h> +#include "color.h" #define COLOR_RESET "\033[m" @@ -902,11 +902,11 @@ void sort_in_topological_order_fn(struct commit_list ** list, int lifo, /* merge-rebase stuff */ -/* bits #0..7 in revision.h */ -#define PARENT1 (1u<< 8) -#define PARENT2 (1u<< 9) -#define STALE (1u<<10) -#define RESULT (1u<<11) +/* bits #0..15 in revision.h */ +#define PARENT1 (1u<<16) +#define PARENT2 (1u<<17) +#define STALE (1u<<18) +#define RESULT (1u<<19) static struct commit *interesting(struct commit_list *list) { @@ -1043,3 +1043,20 @@ struct commit_list *get_merge_bases(struct commit *one, free(rslt); return result; } + +int in_merge_bases(struct commit *rev1, struct commit *rev2) +{ + struct commit_list *bases, *b; + int ret = 0; + + bases = get_merge_bases(rev1, rev2, 1); + for (b = bases; b; b = b->next) { + if (!hashcmp(rev1->object.sha1, b->item->object.sha1)) { + ret = 1; + break; + } + } + + free_commit_list(bases); + return ret; +} @@ -114,4 +114,5 @@ extern int is_repository_shallow(); extern struct commit_list *get_shallow_commits(struct object_array *heads, int depth, int shallow_flag, int not_shallow_flag); +int in_merge_bases(struct commit *rev1, struct commit *rev2); #endif /* COMMIT_H */ diff --git a/compat/inet_ntop.c b/compat/inet_ntop.c index ec8c1bff53..4d7ab9d975 100644 --- a/compat/inet_ntop.c +++ b/compat/inet_ntop.c @@ -93,7 +93,7 @@ inet_ntop6(src, dst, size) */ char tmp[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"], *tp; struct { int base, len; } best, cur; - u_int words[NS_IN6ADDRSZ / NS_INT16SZ]; + unsigned int words[NS_IN6ADDRSZ / NS_INT16SZ]; int i; /* diff --git a/compat/mmap.c b/compat/mmap.c index a4d2e507f7..4cfaee3136 100644 --- a/compat/mmap.c +++ b/compat/mmap.c @@ -1,21 +1,11 @@ -#include <stdio.h> -#include <stdlib.h> -#include <unistd.h> -#include <errno.h> #include "../git-compat-util.h" -void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_t offset) +void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset) { - int n = 0; - off_t current_offset = lseek(fd, 0, SEEK_CUR); + size_t n = 0; if (start != NULL || !(flags & MAP_PRIVATE)) - die("Invalid usage of gitfakemmap."); - - if (lseek(fd, offset, SEEK_SET) < 0) { - errno = EINVAL; - return MAP_FAILED; - } + die("Invalid usage of mmap when built with NO_MMAP"); start = xmalloc(length); if (start == NULL) { @@ -24,14 +14,16 @@ void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_ } while (n < length) { - int count = read(fd, start+n, length-n); + ssize_t count = pread(fd, (char *)start + n, length - n, offset + n); if (count == 0) { - memset(start+n, 0, length-n); + memset((char *)start+n, 0, length-n); break; } if (count < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; free(start); errno = EACCES; return MAP_FAILED; @@ -40,15 +32,10 @@ void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_ n += count; } - if (current_offset != lseek(fd, current_offset, SEEK_SET)) { - errno = EINVAL; - return MAP_FAILED; - } - return start; } -int gitfakemunmap(void *start, size_t length) +int git_munmap(void *start, size_t length) { free(start); return 0; diff --git a/compat/setenv.c b/compat/setenv.c index b7d7678598..3a22ea7b75 100644 --- a/compat/setenv.c +++ b/compat/setenv.c @@ -1,5 +1,4 @@ -#include <stdlib.h> -#include <string.h> +#include "../git-compat-util.h" int gitsetenv(const char *name, const char *value, int replace) { diff --git a/compat/strlcpy.c b/compat/strlcpy.c index b66856a3a5..4024c36030 100644 --- a/compat/strlcpy.c +++ b/compat/strlcpy.c @@ -1,4 +1,4 @@ -#include <string.h> +#include "../git-compat-util.h" size_t gitstrlcpy(char *dest, const char *src, size_t size) { diff --git a/compat/subprocess.py b/compat/subprocess.py deleted file mode 100644 index 6474eab119..0000000000 --- a/compat/subprocess.py +++ /dev/null @@ -1,1149 +0,0 @@ -# subprocess - Subprocesses with accessible I/O streams -# -# For more information about this module, see PEP 324. -# -# This module should remain compatible with Python 2.2, see PEP 291. -# -# Copyright (c) 2003-2005 by Peter Astrand <astrand@lysator.liu.se> -# -# Licensed to PSF under a Contributor Agreement. -# See http://www.python.org/2.4/license for licensing details. - -r"""subprocess - Subprocesses with accessible I/O streams - -This module allows you to spawn processes, connect to their -input/output/error pipes, and obtain their return codes. This module -intends to replace several other, older modules and functions, like: - -os.system -os.spawn* -os.popen* -popen2.* -commands.* - -Information about how the subprocess module can be used to replace these -modules and functions can be found below. - - - -Using the subprocess module -=========================== -This module defines one class called Popen: - -class Popen(args, bufsize=0, executable=None, - stdin=None, stdout=None, stderr=None, - preexec_fn=None, close_fds=False, shell=False, - cwd=None, env=None, universal_newlines=False, - startupinfo=None, creationflags=0): - - -Arguments are: - -args should be a string, or a sequence of program arguments. The -program to execute is normally the first item in the args sequence or -string, but can be explicitly set by using the executable argument. - -On UNIX, with shell=False (default): In this case, the Popen class -uses os.execvp() to execute the child program. args should normally -be a sequence. A string will be treated as a sequence with the string -as the only item (the program to execute). - -On UNIX, with shell=True: If args is a string, it specifies the -command string to execute through the shell. If args is a sequence, -the first item specifies the command string, and any additional items -will be treated as additional shell arguments. - -On Windows: the Popen class uses CreateProcess() to execute the child -program, which operates on strings. If args is a sequence, it will be -converted to a string using the list2cmdline method. Please note that -not all MS Windows applications interpret the command line the same -way: The list2cmdline is designed for applications using the same -rules as the MS C runtime. - -bufsize, if given, has the same meaning as the corresponding argument -to the built-in open() function: 0 means unbuffered, 1 means line -buffered, any other positive value means use a buffer of -(approximately) that size. A negative bufsize means to use the system -default, which usually means fully buffered. The default value for -bufsize is 0 (unbuffered). - -stdin, stdout and stderr specify the executed programs' standard -input, standard output and standard error file handles, respectively. -Valid values are PIPE, an existing file descriptor (a positive -integer), an existing file object, and None. PIPE indicates that a -new pipe to the child should be created. With None, no redirection -will occur; the child's file handles will be inherited from the -parent. Additionally, stderr can be STDOUT, which indicates that the -stderr data from the applications should be captured into the same -file handle as for stdout. - -If preexec_fn is set to a callable object, this object will be called -in the child process just before the child is executed. - -If close_fds is true, all file descriptors except 0, 1 and 2 will be -closed before the child process is executed. - -if shell is true, the specified command will be executed through the -shell. - -If cwd is not None, the current directory will be changed to cwd -before the child is executed. - -If env is not None, it defines the environment variables for the new -process. - -If universal_newlines is true, the file objects stdout and stderr are -opened as a text files, but lines may be terminated by any of '\n', -the Unix end-of-line convention, '\r', the Macintosh convention or -'\r\n', the Windows convention. All of these external representations -are seen as '\n' by the Python program. Note: This feature is only -available if Python is built with universal newline support (the -default). Also, the newlines attribute of the file objects stdout, -stdin and stderr are not updated by the communicate() method. - -The startupinfo and creationflags, if given, will be passed to the -underlying CreateProcess() function. They can specify things such as -appearance of the main window and priority for the new process. -(Windows only) - - -This module also defines two shortcut functions: - -call(*args, **kwargs): - Run command with arguments. Wait for command to complete, then - return the returncode attribute. - - The arguments are the same as for the Popen constructor. Example: - - retcode = call(["ls", "-l"]) - - -Exceptions ----------- -Exceptions raised in the child process, before the new program has -started to execute, will be re-raised in the parent. Additionally, -the exception object will have one extra attribute called -'child_traceback', which is a string containing traceback information -from the childs point of view. - -The most common exception raised is OSError. This occurs, for -example, when trying to execute a non-existent file. Applications -should prepare for OSErrors. - -A ValueError will be raised if Popen is called with invalid arguments. - - -Security --------- -Unlike some other popen functions, this implementation will never call -/bin/sh implicitly. This means that all characters, including shell -metacharacters, can safely be passed to child processes. - - -Popen objects -============= -Instances of the Popen class have the following methods: - -poll() - Check if child process has terminated. Returns returncode - attribute. - -wait() - Wait for child process to terminate. Returns returncode attribute. - -communicate(input=None) - Interact with process: Send data to stdin. Read data from stdout - and stderr, until end-of-file is reached. Wait for process to - terminate. The optional stdin argument should be a string to be - sent to the child process, or None, if no data should be sent to - the child. - - communicate() returns a tuple (stdout, stderr). - - Note: The data read is buffered in memory, so do not use this - method if the data size is large or unlimited. - -The following attributes are also available: - -stdin - If the stdin argument is PIPE, this attribute is a file object - that provides input to the child process. Otherwise, it is None. - -stdout - If the stdout argument is PIPE, this attribute is a file object - that provides output from the child process. Otherwise, it is - None. - -stderr - If the stderr argument is PIPE, this attribute is file object that - provides error output from the child process. Otherwise, it is - None. - -pid - The process ID of the child process. - -returncode - The child return code. A None value indicates that the process - hasn't terminated yet. A negative value -N indicates that the - child was terminated by signal N (UNIX only). - - -Replacing older functions with the subprocess module -==================================================== -In this section, "a ==> b" means that b can be used as a replacement -for a. - -Note: All functions in this section fail (more or less) silently if -the executed program cannot be found; this module raises an OSError -exception. - -In the following examples, we assume that the subprocess module is -imported with "from subprocess import *". - - -Replacing /bin/sh shell backquote ---------------------------------- -output=`mycmd myarg` -==> -output = Popen(["mycmd", "myarg"], stdout=PIPE).communicate()[0] - - -Replacing shell pipe line -------------------------- -output=`dmesg | grep hda` -==> -p1 = Popen(["dmesg"], stdout=PIPE) -p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE) -output = p2.communicate()[0] - - -Replacing os.system() ---------------------- -sts = os.system("mycmd" + " myarg") -==> -p = Popen("mycmd" + " myarg", shell=True) -sts = os.waitpid(p.pid, 0) - -Note: - -* Calling the program through the shell is usually not required. - -* It's easier to look at the returncode attribute than the - exitstatus. - -A more real-world example would look like this: - -try: - retcode = call("mycmd" + " myarg", shell=True) - if retcode < 0: - print >>sys.stderr, "Child was terminated by signal", -retcode - else: - print >>sys.stderr, "Child returned", retcode -except OSError, e: - print >>sys.stderr, "Execution failed:", e - - -Replacing os.spawn* -------------------- -P_NOWAIT example: - -pid = os.spawnlp(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg") -==> -pid = Popen(["/bin/mycmd", "myarg"]).pid - - -P_WAIT example: - -retcode = os.spawnlp(os.P_WAIT, "/bin/mycmd", "mycmd", "myarg") -==> -retcode = call(["/bin/mycmd", "myarg"]) - - -Vector example: - -os.spawnvp(os.P_NOWAIT, path, args) -==> -Popen([path] + args[1:]) - - -Environment example: - -os.spawnlpe(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg", env) -==> -Popen(["/bin/mycmd", "myarg"], env={"PATH": "/usr/bin"}) - - -Replacing os.popen* -------------------- -pipe = os.popen(cmd, mode='r', bufsize) -==> -pipe = Popen(cmd, shell=True, bufsize=bufsize, stdout=PIPE).stdout - -pipe = os.popen(cmd, mode='w', bufsize) -==> -pipe = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE).stdin - - -(child_stdin, child_stdout) = os.popen2(cmd, mode, bufsize) -==> -p = Popen(cmd, shell=True, bufsize=bufsize, - stdin=PIPE, stdout=PIPE, close_fds=True) -(child_stdin, child_stdout) = (p.stdin, p.stdout) - - -(child_stdin, - child_stdout, - child_stderr) = os.popen3(cmd, mode, bufsize) -==> -p = Popen(cmd, shell=True, bufsize=bufsize, - stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) -(child_stdin, - child_stdout, - child_stderr) = (p.stdin, p.stdout, p.stderr) - - -(child_stdin, child_stdout_and_stderr) = os.popen4(cmd, mode, bufsize) -==> -p = Popen(cmd, shell=True, bufsize=bufsize, - stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True) -(child_stdin, child_stdout_and_stderr) = (p.stdin, p.stdout) - - -Replacing popen2.* ------------------- -Note: If the cmd argument to popen2 functions is a string, the command -is executed through /bin/sh. If it is a list, the command is directly -executed. - -(child_stdout, child_stdin) = popen2.popen2("somestring", bufsize, mode) -==> -p = Popen(["somestring"], shell=True, bufsize=bufsize - stdin=PIPE, stdout=PIPE, close_fds=True) -(child_stdout, child_stdin) = (p.stdout, p.stdin) - - -(child_stdout, child_stdin) = popen2.popen2(["mycmd", "myarg"], bufsize, mode) -==> -p = Popen(["mycmd", "myarg"], bufsize=bufsize, - stdin=PIPE, stdout=PIPE, close_fds=True) -(child_stdout, child_stdin) = (p.stdout, p.stdin) - -The popen2.Popen3 and popen3.Popen4 basically works as subprocess.Popen, -except that: - -* subprocess.Popen raises an exception if the execution fails -* the capturestderr argument is replaced with the stderr argument. -* stdin=PIPE and stdout=PIPE must be specified. -* popen2 closes all filedescriptors by default, but you have to specify - close_fds=True with subprocess.Popen. - - -""" - -import sys -mswindows = (sys.platform == "win32") - -import os -import types -import traceback - -if mswindows: - import threading - import msvcrt - if 0: # <-- change this to use pywin32 instead of the _subprocess driver - import pywintypes - from win32api import GetStdHandle, STD_INPUT_HANDLE, \ - STD_OUTPUT_HANDLE, STD_ERROR_HANDLE - from win32api import GetCurrentProcess, DuplicateHandle, \ - GetModuleFileName, GetVersion - from win32con import DUPLICATE_SAME_ACCESS, SW_HIDE - from win32pipe import CreatePipe - from win32process import CreateProcess, STARTUPINFO, \ - GetExitCodeProcess, STARTF_USESTDHANDLES, \ - STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE - from win32event import WaitForSingleObject, INFINITE, WAIT_OBJECT_0 - else: - from _subprocess import * - class STARTUPINFO: - dwFlags = 0 - hStdInput = None - hStdOutput = None - hStdError = None - class pywintypes: - error = IOError -else: - import select - import errno - import fcntl - import pickle - -__all__ = ["Popen", "PIPE", "STDOUT", "call"] - -try: - MAXFD = os.sysconf("SC_OPEN_MAX") -except: - MAXFD = 256 - -# True/False does not exist on 2.2.0 -try: - False -except NameError: - False = 0 - True = 1 - -_active = [] - -def _cleanup(): - for inst in _active[:]: - inst.poll() - -PIPE = -1 -STDOUT = -2 - - -def call(*args, **kwargs): - """Run command with arguments. Wait for command to complete, then - return the returncode attribute. - - The arguments are the same as for the Popen constructor. Example: - - retcode = call(["ls", "-l"]) - """ - return Popen(*args, **kwargs).wait() - - -def list2cmdline(seq): - """ - Translate a sequence of arguments into a command line - string, using the same rules as the MS C runtime: - - 1) Arguments are delimited by white space, which is either a - space or a tab. - - 2) A string surrounded by double quotation marks is - interpreted as a single argument, regardless of white space - contained within. A quoted string can be embedded in an - argument. - - 3) A double quotation mark preceded by a backslash is - interpreted as a literal double quotation mark. - - 4) Backslashes are interpreted literally, unless they - immediately precede a double quotation mark. - - 5) If backslashes immediately precede a double quotation mark, - every pair of backslashes is interpreted as a literal - backslash. If the number of backslashes is odd, the last - backslash escapes the next double quotation mark as - described in rule 3. - """ - - # See - # http://msdn.microsoft.com/library/en-us/vccelng/htm/progs_12.asp - result = [] - needquote = False - for arg in seq: - bs_buf = [] - - # Add a space to separate this argument from the others - if result: - result.append(' ') - - needquote = (" " in arg) or ("\t" in arg) - if needquote: - result.append('"') - - for c in arg: - if c == '\\': - # Don't know if we need to double yet. - bs_buf.append(c) - elif c == '"': - # Double backspaces. - result.append('\\' * len(bs_buf)*2) - bs_buf = [] - result.append('\\"') - else: - # Normal char - if bs_buf: - result.extend(bs_buf) - bs_buf = [] - result.append(c) - - # Add remaining backspaces, if any. - if bs_buf: - result.extend(bs_buf) - - if needquote: - result.extend(bs_buf) - result.append('"') - - return ''.join(result) - - -class Popen(object): - def __init__(self, args, bufsize=0, executable=None, - stdin=None, stdout=None, stderr=None, - preexec_fn=None, close_fds=False, shell=False, - cwd=None, env=None, universal_newlines=False, - startupinfo=None, creationflags=0): - """Create new Popen instance.""" - _cleanup() - - if not isinstance(bufsize, (int, long)): - raise TypeError("bufsize must be an integer") - - if mswindows: - if preexec_fn is not None: - raise ValueError("preexec_fn is not supported on Windows " - "platforms") - if close_fds: - raise ValueError("close_fds is not supported on Windows " - "platforms") - else: - # POSIX - if startupinfo is not None: - raise ValueError("startupinfo is only supported on Windows " - "platforms") - if creationflags != 0: - raise ValueError("creationflags is only supported on Windows " - "platforms") - - self.stdin = None - self.stdout = None - self.stderr = None - self.pid = None - self.returncode = None - self.universal_newlines = universal_newlines - - # Input and output objects. The general principle is like - # this: - # - # Parent Child - # ------ ----- - # p2cwrite ---stdin---> p2cread - # c2pread <--stdout--- c2pwrite - # errread <--stderr--- errwrite - # - # On POSIX, the child objects are file descriptors. On - # Windows, these are Windows file handles. The parent objects - # are file descriptors on both platforms. The parent objects - # are None when not using PIPEs. The child objects are None - # when not redirecting. - - (p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite) = self._get_handles(stdin, stdout, stderr) - - self._execute_child(args, executable, preexec_fn, close_fds, - cwd, env, universal_newlines, - startupinfo, creationflags, shell, - p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite) - - if p2cwrite: - self.stdin = os.fdopen(p2cwrite, 'wb', bufsize) - if c2pread: - if universal_newlines: - self.stdout = os.fdopen(c2pread, 'rU', bufsize) - else: - self.stdout = os.fdopen(c2pread, 'rb', bufsize) - if errread: - if universal_newlines: - self.stderr = os.fdopen(errread, 'rU', bufsize) - else: - self.stderr = os.fdopen(errread, 'rb', bufsize) - - _active.append(self) - - - def _translate_newlines(self, data): - data = data.replace("\r\n", "\n") - data = data.replace("\r", "\n") - return data - - - if mswindows: - # - # Windows methods - # - def _get_handles(self, stdin, stdout, stderr): - """Construct and return tuple with IO objects: - p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite - """ - if stdin == None and stdout == None and stderr == None: - return (None, None, None, None, None, None) - - p2cread, p2cwrite = None, None - c2pread, c2pwrite = None, None - errread, errwrite = None, None - - if stdin == None: - p2cread = GetStdHandle(STD_INPUT_HANDLE) - elif stdin == PIPE: - p2cread, p2cwrite = CreatePipe(None, 0) - # Detach and turn into fd - p2cwrite = p2cwrite.Detach() - p2cwrite = msvcrt.open_osfhandle(p2cwrite, 0) - elif type(stdin) == types.IntType: - p2cread = msvcrt.get_osfhandle(stdin) - else: - # Assuming file-like object - p2cread = msvcrt.get_osfhandle(stdin.fileno()) - p2cread = self._make_inheritable(p2cread) - - if stdout == None: - c2pwrite = GetStdHandle(STD_OUTPUT_HANDLE) - elif stdout == PIPE: - c2pread, c2pwrite = CreatePipe(None, 0) - # Detach and turn into fd - c2pread = c2pread.Detach() - c2pread = msvcrt.open_osfhandle(c2pread, 0) - elif type(stdout) == types.IntType: - c2pwrite = msvcrt.get_osfhandle(stdout) - else: - # Assuming file-like object - c2pwrite = msvcrt.get_osfhandle(stdout.fileno()) - c2pwrite = self._make_inheritable(c2pwrite) - - if stderr == None: - errwrite = GetStdHandle(STD_ERROR_HANDLE) - elif stderr == PIPE: - errread, errwrite = CreatePipe(None, 0) - # Detach and turn into fd - errread = errread.Detach() - errread = msvcrt.open_osfhandle(errread, 0) - elif stderr == STDOUT: - errwrite = c2pwrite - elif type(stderr) == types.IntType: - errwrite = msvcrt.get_osfhandle(stderr) - else: - # Assuming file-like object - errwrite = msvcrt.get_osfhandle(stderr.fileno()) - errwrite = self._make_inheritable(errwrite) - - return (p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite) - - - def _make_inheritable(self, handle): - """Return a duplicate of handle, which is inheritable""" - return DuplicateHandle(GetCurrentProcess(), handle, - GetCurrentProcess(), 0, 1, - DUPLICATE_SAME_ACCESS) - - - def _find_w9xpopen(self): - """Find and return absolute path to w9xpopen.exe""" - w9xpopen = os.path.join(os.path.dirname(GetModuleFileName(0)), - "w9xpopen.exe") - if not os.path.exists(w9xpopen): - # Eeek - file-not-found - possibly an embedding - # situation - see if we can locate it in sys.exec_prefix - w9xpopen = os.path.join(os.path.dirname(sys.exec_prefix), - "w9xpopen.exe") - if not os.path.exists(w9xpopen): - raise RuntimeError("Cannot locate w9xpopen.exe, which is " - "needed for Popen to work with your " - "shell or platform.") - return w9xpopen - - - def _execute_child(self, args, executable, preexec_fn, close_fds, - cwd, env, universal_newlines, - startupinfo, creationflags, shell, - p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite): - """Execute program (MS Windows version)""" - - if not isinstance(args, types.StringTypes): - args = list2cmdline(args) - - # Process startup details - default_startupinfo = STARTUPINFO() - if startupinfo == None: - startupinfo = default_startupinfo - if not None in (p2cread, c2pwrite, errwrite): - startupinfo.dwFlags |= STARTF_USESTDHANDLES - startupinfo.hStdInput = p2cread - startupinfo.hStdOutput = c2pwrite - startupinfo.hStdError = errwrite - - if shell: - default_startupinfo.dwFlags |= STARTF_USESHOWWINDOW - default_startupinfo.wShowWindow = SW_HIDE - comspec = os.environ.get("COMSPEC", "cmd.exe") - args = comspec + " /c " + args - if (GetVersion() >= 0x80000000L or - os.path.basename(comspec).lower() == "command.com"): - # Win9x, or using command.com on NT. We need to - # use the w9xpopen intermediate program. For more - # information, see KB Q150956 - # (http://web.archive.org/web/20011105084002/http://support.microsoft.com/support/kb/articles/Q150/9/56.asp) - w9xpopen = self._find_w9xpopen() - args = '"%s" %s' % (w9xpopen, args) - # Not passing CREATE_NEW_CONSOLE has been known to - # cause random failures on win9x. Specifically a - # dialog: "Your program accessed mem currently in - # use at xxx" and a hopeful warning about the - # stability of your system. Cost is Ctrl+C wont - # kill children. - creationflags |= CREATE_NEW_CONSOLE - - # Start the process - try: - hp, ht, pid, tid = CreateProcess(executable, args, - # no special security - None, None, - # must inherit handles to pass std - # handles - 1, - creationflags, - env, - cwd, - startupinfo) - except pywintypes.error, e: - # Translate pywintypes.error to WindowsError, which is - # a subclass of OSError. FIXME: We should really - # translate errno using _sys_errlist (or simliar), but - # how can this be done from Python? - raise WindowsError(*e.args) - - # Retain the process handle, but close the thread handle - self._handle = hp - self.pid = pid - ht.Close() - - # Child is launched. Close the parent's copy of those pipe - # handles that only the child should have open. You need - # to make sure that no handles to the write end of the - # output pipe are maintained in this process or else the - # pipe will not close when the child process exits and the - # ReadFile will hang. - if p2cread != None: - p2cread.Close() - if c2pwrite != None: - c2pwrite.Close() - if errwrite != None: - errwrite.Close() - - - def poll(self): - """Check if child process has terminated. Returns returncode - attribute.""" - if self.returncode == None: - if WaitForSingleObject(self._handle, 0) == WAIT_OBJECT_0: - self.returncode = GetExitCodeProcess(self._handle) - _active.remove(self) - return self.returncode - - - def wait(self): - """Wait for child process to terminate. Returns returncode - attribute.""" - if self.returncode == None: - obj = WaitForSingleObject(self._handle, INFINITE) - self.returncode = GetExitCodeProcess(self._handle) - _active.remove(self) - return self.returncode - - - def _readerthread(self, fh, buffer): - buffer.append(fh.read()) - - - def communicate(self, input=None): - """Interact with process: Send data to stdin. Read data from - stdout and stderr, until end-of-file is reached. Wait for - process to terminate. The optional input argument should be a - string to be sent to the child process, or None, if no data - should be sent to the child. - - communicate() returns a tuple (stdout, stderr).""" - stdout = None # Return - stderr = None # Return - - if self.stdout: - stdout = [] - stdout_thread = threading.Thread(target=self._readerthread, - args=(self.stdout, stdout)) - stdout_thread.setDaemon(True) - stdout_thread.start() - if self.stderr: - stderr = [] - stderr_thread = threading.Thread(target=self._readerthread, - args=(self.stderr, stderr)) - stderr_thread.setDaemon(True) - stderr_thread.start() - - if self.stdin: - if input != None: - self.stdin.write(input) - self.stdin.close() - - if self.stdout: - stdout_thread.join() - if self.stderr: - stderr_thread.join() - - # All data exchanged. Translate lists into strings. - if stdout != None: - stdout = stdout[0] - if stderr != None: - stderr = stderr[0] - - # Translate newlines, if requested. We cannot let the file - # object do the translation: It is based on stdio, which is - # impossible to combine with select (unless forcing no - # buffering). - if self.universal_newlines and hasattr(open, 'newlines'): - if stdout: - stdout = self._translate_newlines(stdout) - if stderr: - stderr = self._translate_newlines(stderr) - - self.wait() - return (stdout, stderr) - - else: - # - # POSIX methods - # - def _get_handles(self, stdin, stdout, stderr): - """Construct and return tuple with IO objects: - p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite - """ - p2cread, p2cwrite = None, None - c2pread, c2pwrite = None, None - errread, errwrite = None, None - - if stdin == None: - pass - elif stdin == PIPE: - p2cread, p2cwrite = os.pipe() - elif type(stdin) == types.IntType: - p2cread = stdin - else: - # Assuming file-like object - p2cread = stdin.fileno() - - if stdout == None: - pass - elif stdout == PIPE: - c2pread, c2pwrite = os.pipe() - elif type(stdout) == types.IntType: - c2pwrite = stdout - else: - # Assuming file-like object - c2pwrite = stdout.fileno() - - if stderr == None: - pass - elif stderr == PIPE: - errread, errwrite = os.pipe() - elif stderr == STDOUT: - errwrite = c2pwrite - elif type(stderr) == types.IntType: - errwrite = stderr - else: - # Assuming file-like object - errwrite = stderr.fileno() - - return (p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite) - - - def _set_cloexec_flag(self, fd): - try: - cloexec_flag = fcntl.FD_CLOEXEC - except AttributeError: - cloexec_flag = 1 - - old = fcntl.fcntl(fd, fcntl.F_GETFD) - fcntl.fcntl(fd, fcntl.F_SETFD, old | cloexec_flag) - - - def _close_fds(self, but): - for i in range(3, MAXFD): - if i == but: - continue - try: - os.close(i) - except: - pass - - - def _execute_child(self, args, executable, preexec_fn, close_fds, - cwd, env, universal_newlines, - startupinfo, creationflags, shell, - p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite): - """Execute program (POSIX version)""" - - if isinstance(args, types.StringTypes): - args = [args] - - if shell: - args = ["/bin/sh", "-c"] + args - - if executable == None: - executable = args[0] - - # For transferring possible exec failure from child to parent - # The first char specifies the exception type: 0 means - # OSError, 1 means some other error. - errpipe_read, errpipe_write = os.pipe() - self._set_cloexec_flag(errpipe_write) - - self.pid = os.fork() - if self.pid == 0: - # Child - try: - # Close parent's pipe ends - if p2cwrite: - os.close(p2cwrite) - if c2pread: - os.close(c2pread) - if errread: - os.close(errread) - os.close(errpipe_read) - - # Dup fds for child - if p2cread: - os.dup2(p2cread, 0) - if c2pwrite: - os.dup2(c2pwrite, 1) - if errwrite: - os.dup2(errwrite, 2) - - # Close pipe fds. Make sure we doesn't close the same - # fd more than once. - if p2cread: - os.close(p2cread) - if c2pwrite and c2pwrite not in (p2cread,): - os.close(c2pwrite) - if errwrite and errwrite not in (p2cread, c2pwrite): - os.close(errwrite) - - # Close all other fds, if asked for - if close_fds: - self._close_fds(but=errpipe_write) - - if cwd != None: - os.chdir(cwd) - - if preexec_fn: - apply(preexec_fn) - - if env == None: - os.execvp(executable, args) - else: - os.execvpe(executable, args, env) - - except: - exc_type, exc_value, tb = sys.exc_info() - # Save the traceback and attach it to the exception object - exc_lines = traceback.format_exception(exc_type, - exc_value, - tb) - exc_value.child_traceback = ''.join(exc_lines) - os.write(errpipe_write, pickle.dumps(exc_value)) - - # This exitcode won't be reported to applications, so it - # really doesn't matter what we return. - os._exit(255) - - # Parent - os.close(errpipe_write) - if p2cread and p2cwrite: - os.close(p2cread) - if c2pwrite and c2pread: - os.close(c2pwrite) - if errwrite and errread: - os.close(errwrite) - - # Wait for exec to fail or succeed; possibly raising exception - data = os.read(errpipe_read, 1048576) # Exceptions limited to 1 MB - os.close(errpipe_read) - if data != "": - os.waitpid(self.pid, 0) - child_exception = pickle.loads(data) - raise child_exception - - - def _handle_exitstatus(self, sts): - if os.WIFSIGNALED(sts): - self.returncode = -os.WTERMSIG(sts) - elif os.WIFEXITED(sts): - self.returncode = os.WEXITSTATUS(sts) - else: - # Should never happen - raise RuntimeError("Unknown child exit status!") - - _active.remove(self) - - - def poll(self): - """Check if child process has terminated. Returns returncode - attribute.""" - if self.returncode == None: - try: - pid, sts = os.waitpid(self.pid, os.WNOHANG) - if pid == self.pid: - self._handle_exitstatus(sts) - except os.error: - pass - return self.returncode - - - def wait(self): - """Wait for child process to terminate. Returns returncode - attribute.""" - if self.returncode == None: - pid, sts = os.waitpid(self.pid, 0) - self._handle_exitstatus(sts) - return self.returncode - - - def communicate(self, input=None): - """Interact with process: Send data to stdin. Read data from - stdout and stderr, until end-of-file is reached. Wait for - process to terminate. The optional input argument should be a - string to be sent to the child process, or None, if no data - should be sent to the child. - - communicate() returns a tuple (stdout, stderr).""" - read_set = [] - write_set = [] - stdout = None # Return - stderr = None # Return - - if self.stdin: - # Flush stdio buffer. This might block, if the user has - # been writing to .stdin in an uncontrolled fashion. - self.stdin.flush() - if input: - write_set.append(self.stdin) - else: - self.stdin.close() - if self.stdout: - read_set.append(self.stdout) - stdout = [] - if self.stderr: - read_set.append(self.stderr) - stderr = [] - - while read_set or write_set: - rlist, wlist, xlist = select.select(read_set, write_set, []) - - if self.stdin in wlist: - # When select has indicated that the file is writable, - # we can write up to PIPE_BUF bytes without risk - # blocking. POSIX defines PIPE_BUF >= 512 - bytes_written = os.write(self.stdin.fileno(), input[:512]) - input = input[bytes_written:] - if not input: - self.stdin.close() - write_set.remove(self.stdin) - - if self.stdout in rlist: - data = os.read(self.stdout.fileno(), 1024) - if data == "": - self.stdout.close() - read_set.remove(self.stdout) - stdout.append(data) - - if self.stderr in rlist: - data = os.read(self.stderr.fileno(), 1024) - if data == "": - self.stderr.close() - read_set.remove(self.stderr) - stderr.append(data) - - # All data exchanged. Translate lists into strings. - if stdout != None: - stdout = ''.join(stdout) - if stderr != None: - stderr = ''.join(stderr) - - # Translate newlines, if requested. We cannot let the file - # object do the translation: It is based on stdio, which is - # impossible to combine with select (unless forcing no - # buffering). - if self.universal_newlines and hasattr(open, 'newlines'): - if stdout: - stdout = self._translate_newlines(stdout) - if stderr: - stderr = self._translate_newlines(stderr) - - self.wait() - return (stdout, stderr) - - -def _demo_posix(): - # - # Example 1: Simple redirection: Get process list - # - plist = Popen(["ps"], stdout=PIPE).communicate()[0] - print "Process list:" - print plist - - # - # Example 2: Change uid before executing child - # - if os.getuid() == 0: - p = Popen(["id"], preexec_fn=lambda: os.setuid(100)) - p.wait() - - # - # Example 3: Connecting several subprocesses - # - print "Looking for 'hda'..." - p1 = Popen(["dmesg"], stdout=PIPE) - p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE) - print repr(p2.communicate()[0]) - - # - # Example 4: Catch execution error - # - print - print "Trying a weird file..." - try: - print Popen(["/this/path/does/not/exist"]).communicate() - except OSError, e: - if e.errno == errno.ENOENT: - print "The file didn't exist. I thought so..." - print "Child traceback:" - print e.child_traceback - else: - print "Error", e.errno - else: - print >>sys.stderr, "Gosh. No error." - - -def _demo_windows(): - # - # Example 1: Connecting several subprocesses - # - print "Looking for 'PROMPT' in set output..." - p1 = Popen("set", stdout=PIPE, shell=True) - p2 = Popen('find "PROMPT"', stdin=p1.stdout, stdout=PIPE) - print repr(p2.communicate()[0]) - - # - # Example 2: Simple execution of program - # - print "Executing calc..." - p = Popen("calc") - p.wait() - - -if __name__ == "__main__": - if mswindows: - _demo_windows() - else: - _demo_posix() diff --git a/compat/unsetenv.c b/compat/unsetenv.c index 3a5e4ec04a..eb29f5e084 100644 --- a/compat/unsetenv.c +++ b/compat/unsetenv.c @@ -1,5 +1,4 @@ -#include <stdlib.h> -#include <string.h> +#include "../git-compat-util.h" void gitunsetenv (const char *name) { @@ -6,7 +6,6 @@ * */ #include "cache.h" -#include <regex.h> #define MAXNAME (256) @@ -314,7 +313,7 @@ int git_default_config(const char *var, const char *value) return 0; } - if (!strcmp(var, "pager.color")) { + if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) { pager_use_color = git_config_bool(var,value); return 0; } @@ -350,10 +349,10 @@ int git_config(config_fn_t fn) * $GIT_CONFIG_LOCAL will make it process it in addition to the * global config file, the same way it would the per-repository * config file otherwise. */ - filename = getenv("GIT_CONFIG"); + filename = getenv(CONFIG_ENVIRONMENT); if (!filename) { home = getenv("HOME"); - filename = getenv("GIT_CONFIG_LOCAL"); + filename = getenv(CONFIG_LOCAL_ENVIRONMENT); if (!filename) filename = repo_config = xstrdup(git_path("config")); } @@ -544,9 +543,9 @@ int git_config_set_multivar(const char* key, const char* value, char* lock_file; const char* last_dot = strrchr(key, '.'); - config_filename = getenv("GIT_CONFIG"); + config_filename = getenv(CONFIG_ENVIRONMENT); if (!config_filename) { - config_filename = getenv("GIT_CONFIG_LOCAL"); + config_filename = getenv(CONFIG_LOCAL_ENVIRONMENT); if (!config_filename) config_filename = git_path("config"); } @@ -746,4 +745,75 @@ out_free: return ret; } +int git_config_rename_section(const char *old_name, const char *new_name) +{ + int ret = 0; + char *config_filename; + struct lock_file *lock = xcalloc(sizeof(struct lock_file), 1); + int out_fd; + char buf[1024]; + + config_filename = getenv(CONFIG_ENVIRONMENT); + if (!config_filename) { + config_filename = getenv(CONFIG_LOCAL_ENVIRONMENT); + if (!config_filename) + config_filename = git_path("config"); + } + config_filename = xstrdup(config_filename); + out_fd = hold_lock_file_for_update(lock, config_filename, 0); + if (out_fd < 0) { + ret = error("Could not lock config file!"); + goto out; + } + + if (!(config_file = fopen(config_filename, "rb"))) { + ret = error("Could not open config file!"); + goto out; + } + + while (fgets(buf, sizeof(buf), config_file)) { + int i; + for (i = 0; buf[i] && isspace(buf[i]); i++) + ; /* do nothing */ + if (buf[i] == '[') { + /* it's a section */ + int j = 0, dot = 0; + for (i++; buf[i] && buf[i] != ']'; i++) { + if (!dot && isspace(buf[i])) { + dot = 1; + if (old_name[j++] != '.') + break; + for (i++; isspace(buf[i]); i++) + ; /* do nothing */ + if (buf[i] != '"') + break; + continue; + } + if (buf[i] == '\\' && dot) + i++; + else if (buf[i] == '"' && dot) { + for (i++; isspace(buf[i]); i++) + ; /* do_nothing */ + break; + } + if (buf[i] != old_name[j++]) + break; + } + if (buf[i] == ']') { + /* old_name matches */ + ret++; + store.baselen = strlen(new_name); + store_write_section(out_fd, new_name); + continue; + } + } + write(out_fd, buf, strlen(buf)); + } + fclose(config_file); + if (close(out_fd) || commit_lock_file(lock) < 0) + ret = error("Cannot commit config file!"); + out: + free(config_filename); + return ret; +} diff --git a/config.mak.in b/config.mak.in index 1cafa19ed4..9a578405d8 100644 --- a/config.mak.in +++ b/config.mak.in @@ -13,7 +13,6 @@ bindir = @bindir@ #gitexecdir = @libexecdir@/git-core/ datarootdir = @datarootdir@ template_dir = @datadir@/git-core/templates/ -GIT_PYTHON_DIR = @datadir@/git-core/python mandir=@mandir@ @@ -23,7 +22,6 @@ VPATH = @srcdir@ export exec_prefix mandir export srcdir VPATH -NO_PYTHON=@NO_PYTHON@ NEEDS_SSL_WITH_CRYPTO=@NEEDS_SSL_WITH_CRYPTO@ NO_OPENSSL=@NO_OPENSSL@ NO_CURL=@NO_CURL@ diff --git a/configure.ac b/configure.ac index cff5722eb9..7cfb3a0666 100644 --- a/configure.ac +++ b/configure.ac @@ -75,20 +75,6 @@ GIT_ARG_SET_PATH(shell) # Define PERL_PATH to provide path to Perl. GIT_ARG_SET_PATH(perl) # -# Define PYTHON_PATH to provide path to Python. -AC_ARG_WITH(python,[AS_HELP_STRING([--with-python=PATH], [provide PATH to python]) -AS_HELP_STRING([--without-python], [don't use python scripts])], - [if test "$withval" = "no"; then \ - NO_PYTHON=YesPlease; \ - elif test "$withval" = "yes"; then \ - NO_PYTHON=; \ - else \ - NO_PYTHON=; \ - PYTHON_PATH=$withval; \ - fi; \ - ]) -AC_SUBST(NO_PYTHON) -AC_SUBST(PYTHON_PATH) ## Checks for programs. @@ -98,18 +84,6 @@ AC_PROG_CC([cc gcc]) #AC_PROG_INSTALL # needs install-sh or install.sh in sources AC_CHECK_TOOL(AR, ar, :) AC_CHECK_PROGS(TAR, [gtar tar]) -# -# Define PYTHON_PATH to provide path to Python. -if test -z "$NO_PYTHON"; then - if test -z "$PYTHON_PATH"; then - AC_PATH_PROGS(PYTHON_PATH, [python python2.4 python2.3 python2]) - fi - if test -n "$PYTHON_PATH"; then - GIT_CONF_APPEND_LINE([PYTHON_PATH=@PYTHON_PATH@]) - NO_PYTHON="" - fi -fi - ## Checks for libraries. AC_MSG_NOTICE([CHECKS for libraries]) @@ -261,22 +235,6 @@ AC_SUBST(NO_SETENV) # # Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link. # Enable it on Windows. By default, symrefs are still used. -# -# Define WITH_OWN_SUBPROCESS_PY if you want to use with python 2.3. -AC_CACHE_CHECK([for subprocess.py], - [ac_cv_python_has_subprocess_py], -[if $PYTHON_PATH -c 'import subprocess' 2>/dev/null; then - ac_cv_python_has_subprocess_py=yes -else - ac_cv_python_has_subprocess_py=no -fi]) -if test $ac_cv_python_has_subprocess_py != yes; then - GIT_CONF_APPEND_LINE([WITH_OWN_SUBPROCESS_PY=YesPlease]) -fi -# -# Define NO_ACCURATE_DIFF if your diff program at least sometimes misses -# a missing newline at the end of the file. - ## Site configuration (override autodetection) ## --with-PACKAGE[=ARG] and --without-PACKAGE @@ -355,10 +313,6 @@ GIT_PARSE_WITH(iconv)) ## --enable-FEATURE[=ARG] and --disable-FEATURE # -# Define COLLISION_CHECK below if you believe that SHA1's -# 1461501637330902918203684832716283019655932542976 hashes do not give you -# sufficient guarantee that no collisions between objects will ever happen. -# # Define USE_NSEC below if you want git to care about sub-second file mtimes # and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and # it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely @@ -3,12 +3,6 @@ #include "pkt-line.h" #include "quote.h" #include "refs.h" -#include <sys/wait.h> -#include <sys/socket.h> -#include <netinet/in.h> -#include <arpa/inet.h> -#include <netdb.h> -#include <signal.h> static char *server_capabilities; @@ -144,6 +138,7 @@ struct refspec { * +A:B means overwrite remote B with local A. * +A is a shorthand for +A:A. * A is a shorthand for A:A. + * :B means delete remote B. */ static struct refspec *parse_ref_spec(int nr_refspec, char **refspec) { @@ -240,6 +235,13 @@ static struct ref *try_explicit_object_name(const char *name) unsigned char sha1[20]; struct ref *ref; int len; + + if (!*name) { + ref = xcalloc(1, sizeof(*ref) + 20); + strcpy(ref->name, "(delete)"); + hashclr(ref->new_sha1); + return ref; + } if (get_sha1(name, sha1)) return NULL; len = strlen(name) + 1; @@ -262,7 +264,8 @@ static int match_explicit_refs(struct ref *src, struct ref *dst, break; case 0: /* The source could be in the get_sha1() format - * not a reference name. + * not a reference name. :refs/other is a + * way to delete 'other' ref at the remote end. */ matched_src = try_explicit_object_name(rs[i].src); if (matched_src) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index a43a177160..234cd0954b 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -18,26 +18,94 @@ # 2) Added the following line to your .bashrc: # source ~/.git-completion.sh # +# 3) You may want to make sure the git executable is available +# in your PATH before this script is sourced, as some caching +# is performed while the script loads. If git isn't found +# at source time then all lookups will be done on demand, +# which may be slightly slower. +# +# 4) Consider changing your PS1 to also show the current branch: +# PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ ' +# +# The argument to __git_ps1 will be displayed only if you +# are currently in a git repository. The %s token will be +# the name of the current branch. +# __gitdir () { - echo "${__git_dir:-$(git rev-parse --git-dir 2>/dev/null)}" + if [ -z "$1" ]; then + if [ -n "$__git_dir" ]; then + echo "$__git_dir" + elif [ -d .git ]; then + echo .git + else + git rev-parse --git-dir 2>/dev/null + fi + elif [ -d "$1/.git" ]; then + echo "$1/.git" + else + echo "$1" + fi +} + +__git_ps1 () +{ + local b="$(git symbolic-ref HEAD 2>/dev/null)" + if [ -n "$b" ]; then + if [ -n "$1" ]; then + printf "$1" "${b##refs/heads/}" + else + printf " (%s)" "${b##refs/heads/}" + fi + fi +} + +__git_heads () +{ + local cmd i is_hash=y dir="$(__gitdir "$1")" + if [ -d "$dir" ]; then + for i in $(git --git-dir="$dir" \ + for-each-ref --format='%(refname)' \ + refs/heads ); do + echo "${i#refs/heads/}" + done + return + fi + for i in $(git-ls-remote "$1" 2>/dev/null); do + case "$is_hash,$i" in + y,*) is_hash=n ;; + n,*^{}) is_hash=y ;; + n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;; + n,*) is_hash=y; echo "$i" ;; + esac + done } __git_refs () { - local cmd i is_hash=y dir="${1:-$(__gitdir)}" + local cmd i is_hash=y dir="$(__gitdir "$1")" if [ -d "$dir" ]; then - cmd=git-peek-remote - else - cmd=git-ls-remote + if [ -e "$dir/HEAD" ]; then echo HEAD; fi + for i in $(git --git-dir="$dir" \ + for-each-ref --format='%(refname)' \ + refs/tags refs/heads refs/remotes); do + case "$i" in + refs/tags/*) echo "${i#refs/tags/}" ;; + refs/heads/*) echo "${i#refs/heads/}" ;; + refs/remotes/*) echo "${i#refs/remotes/}" ;; + *) echo "$i" ;; + esac + done + return fi - for i in $($cmd "$dir" 2>/dev/null); do + for i in $(git-ls-remote "$dir" 2>/dev/null); do case "$is_hash,$i" in y,*) is_hash=n ;; n,*^{}) is_hash=y ;; n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}" ;; n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;; + n,refs/remotes/*) is_hash=y; echo "${i#refs/remotes/}" ;; n,*) is_hash=y; echo "$i" ;; esac done @@ -45,19 +113,25 @@ __git_refs () __git_refs2 () { - local cmd i is_hash=y dir="${1:-$(__gitdir)}" - if [ -d "$dir" ]; then - cmd=git-peek-remote - else - cmd=git-ls-remote - fi - for i in $($cmd "$dir" 2>/dev/null); do + local i + for i in $(__git_refs "$1"); do + echo "$i:$i" + done +} + +__git_refs_remotes () +{ + local cmd i is_hash=y + for i in $(git-ls-remote "$1" 2>/dev/null); do case "$is_hash,$i" in + n,refs/heads/*) + is_hash=y + echo "$i:refs/remotes/$1/${i#refs/heads/}" + ;; y,*) is_hash=n ;; n,*^{}) is_hash=y ;; - n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}:${i#refs/tags/}" ;; - n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}:${i#refs/heads/}" ;; - n,*) is_hash=y; echo "$i:$i" ;; + n,refs/tags/*) is_hash=y;; + n,*) is_hash=y; ;; esac done } @@ -81,6 +155,22 @@ __git_remotes () done } +__git_merge_strategies () +{ + if [ -n "$__git_merge_strategylist" ]; then + echo "$__git_merge_strategylist" + return + fi + sed -n "/^all_strategies='/{ + s/^all_strategies='// + s/'// + p + q + }" "$(git --exec-path)/git-merge" +} +__git_merge_strategylist= +__git_merge_strategylist="$(__git_merge_strategies 2>/dev/null)" + __git_complete_file () { local pfx ls ref cur="${COMP_WORDS[COMP_CWORD]}" @@ -115,6 +205,84 @@ __git_complete_file () esac } +__git_complete_revlist () +{ + local pfx cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + *...*) + pfx="${cur%...*}..." + cur="${cur#*...}" + COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur")) + ;; + *..*) + pfx="${cur%..*}.." + cur="${cur#*..}" + COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur")) + ;; + *) + COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) + ;; + esac +} + +__git_commands () +{ + if [ -n "$__git_commandlist" ]; then + echo "$__git_commandlist" + return + fi + local i IFS=" "$'\n' + for i in $(git help -a|egrep '^ ') + do + case $i in + check-ref-format) : plumbing;; + commit-tree) : plumbing;; + convert-objects) : plumbing;; + cvsserver) : daemon;; + daemon) : daemon;; + fetch-pack) : plumbing;; + hash-object) : plumbing;; + http-*) : transport;; + index-pack) : plumbing;; + local-fetch) : plumbing;; + mailinfo) : plumbing;; + mailsplit) : plumbing;; + merge-*) : plumbing;; + mktree) : plumbing;; + mktag) : plumbing;; + pack-objects) : plumbing;; + pack-redundant) : plumbing;; + pack-refs) : plumbing;; + parse-remote) : plumbing;; + patch-id) : plumbing;; + peek-remote) : plumbing;; + read-tree) : plumbing;; + receive-pack) : plumbing;; + rerere) : plumbing;; + rev-list) : plumbing;; + rev-parse) : plumbing;; + runstatus) : plumbing;; + sh-setup) : internal;; + shell) : daemon;; + send-pack) : plumbing;; + show-index) : plumbing;; + ssh-*) : transport;; + stripspace) : plumbing;; + symbolic-ref) : plumbing;; + unpack-file) : plumbing;; + unpack-objects) : plumbing;; + update-ref) : plumbing;; + update-server-info) : daemon;; + upload-archive) : plumbing;; + upload-pack) : plumbing;; + write-tree) : plumbing;; + *) echo $i;; + esac + done +} +__git_commandlist= +__git_commandlist="$(__git_commands 2>/dev/null)" + __git_aliases () { local i IFS=$'\n' @@ -140,6 +308,54 @@ __git_aliased_command () done } +__git_whitespacelist="nowarn warn error error-all strip" + +_git_am () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + if [ -d .dotest ]; then + COMPREPLY=($(compgen -W " + --skip --resolved + " -- "$cur")) + return + fi + case "$cur" in + --whitespace=*) + COMPREPLY=($(compgen -W "$__git_whitespacelist" \ + -- "${cur##--whitespace=}")) + return + ;; + --*) + COMPREPLY=($(compgen -W " + --signoff --utf8 --binary --3way --interactive + --whitespace= + " -- "$cur")) + return + esac + COMPREPLY=() +} + +_git_apply () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + --whitespace=*) + COMPREPLY=($(compgen -W "$__git_whitespacelist" \ + -- "${cur##--whitespace=}")) + return + ;; + --*) + COMPREPLY=($(compgen -W " + --stat --numstat --summary --check --index + --cached --index-info --reverse --reject --unidiff-zero + --apply --no-add --exclude= + --whitespace= --inaccurate-eof --verbose + " -- "$cur")) + return + esac + COMPREPLY=() +} + _git_branch () { local cur="${COMP_WORDS[COMP_CWORD]}" @@ -168,6 +384,35 @@ _git_checkout () COMPREPLY=($(compgen -W "-l -b $(__git_refs)" -- "$cur")) } +_git_cherry_pick () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + --*) + COMPREPLY=($(compgen -W " + --edit --no-commit + " -- "$cur")) + ;; + *) + COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) + ;; + esac +} + +_git_commit () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + --*) + COMPREPLY=($(compgen -W " + --all --author= --signoff --verify --no-verify + --edit --amend --include --only + " -- "$cur")) + return + esac + COMPREPLY=() +} + _git_diff () { __git_complete_file @@ -209,6 +454,26 @@ _git_fetch () esac } +_git_format_patch () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + --*) + COMPREPLY=($(compgen -W " + --stdout --attach --thread + --output-directory + --numbered --start-number + --keep-subject + --signoff + --in-reply-to= + --full-index --binary + " -- "$cur")) + return + ;; + esac + __git_complete_revlist +} + _git_ls_remote () { local cur="${COMP_WORDS[COMP_CWORD]}" @@ -222,22 +487,53 @@ _git_ls_tree () _git_log () { - local pfx cur="${COMP_WORDS[COMP_CWORD]}" + local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in - *...*) - pfx="${cur%...*}..." - cur="${cur#*...}" - COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur")) + --pretty=*) + COMPREPLY=($(compgen -W " + oneline short medium full fuller email raw + " -- "${cur##--pretty=}")) + return ;; - *..*) - pfx="${cur%..*}.." - cur="${cur#*..}" - COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur")) + --*) + COMPREPLY=($(compgen -W " + --max-count= --max-age= --since= --after= + --min-age= --before= --until= + --root --not --topo-order --date-order + --no-merges + --abbrev-commit --abbrev= + --relative-date + --author= --committer= --grep= + --all-match + --pretty= --name-status --name-only + " -- "$cur")) + return ;; - *) - COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) + esac + __git_complete_revlist +} + +_git_merge () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + case "${COMP_WORDS[COMP_CWORD-1]}" in + -s|--strategy) + COMPREPLY=($(compgen -W "$(__git_merge_strategies)" -- "$cur")) + return + esac + case "$cur" in + --strategy=*) + COMPREPLY=($(compgen -W "$(__git_merge_strategies)" \ + -- "${cur##--strategy=}")) + return ;; + --*) + COMPREPLY=($(compgen -W " + --no-commit --no-summary --squash --strategy + " -- "$cur")) + return esac + COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) } _git_merge_base () @@ -246,6 +542,12 @@ _git_merge_base () COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) } +_git_name_rev () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + COMPREPLY=($(compgen -W "--tags --all --stdin" -- "$cur")) +} + _git_pull () { local cur="${COMP_WORDS[COMP_CWORD]}" @@ -298,6 +600,151 @@ _git_push () esac } +_git_rebase () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + if [ -d .dotest ]; then + COMPREPLY=($(compgen -W " + --continue --skip --abort + " -- "$cur")) + return + fi + case "${COMP_WORDS[COMP_CWORD-1]}" in + -s|--strategy) + COMPREPLY=($(compgen -W "$(__git_merge_strategies)" -- "$cur")) + return + esac + case "$cur" in + --strategy=*) + COMPREPLY=($(compgen -W "$(__git_merge_strategies)" \ + -- "${cur##--strategy=}")) + return + ;; + --*) + COMPREPLY=($(compgen -W " + --onto --merge --strategy + " -- "$cur")) + return + esac + COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) +} + +_git_repo_config () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + local prv="${COMP_WORDS[COMP_CWORD-1]}" + case "$prv" in + branch.*.remote) + COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur")) + return + ;; + branch.*.merge) + COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) + return + ;; + remote.*.fetch) + local remote="${prv#remote.}" + remote="${remote%.fetch}" + COMPREPLY=($(compgen -W "$(__git_refs_remotes "$remote")" \ + -- "$cur")) + return + ;; + remote.*.push) + local remote="${prv#remote.}" + remote="${remote%.push}" + COMPREPLY=($(compgen -W "$(git --git-dir="$(__gitdir)" \ + for-each-ref --format='%(refname):%(refname)' \ + refs/heads)" -- "$cur")) + return + ;; + *.*) + COMPREPLY=() + return + ;; + esac + case "$cur" in + --*) + COMPREPLY=($(compgen -W " + --global --list --replace-all + --get --get-all --get-regexp + --unset --unset-all + " -- "$cur")) + return + ;; + branch.*.*) + local pfx="${cur%.*}." + cur="${cur##*.}" + COMPREPLY=($(compgen -P "$pfx" -W "remote merge" -- "$cur")) + return + ;; + branch.*) + local pfx="${cur%.*}." + cur="${cur#*.}" + COMPREPLY=($(compgen -P "$pfx" -S . \ + -W "$(__git_heads)" -- "$cur")) + return + ;; + remote.*.*) + local pfx="${cur%.*}." + cur="${cur##*.}" + COMPREPLY=($(compgen -P "$pfx" -W "url fetch push" -- "$cur")) + return + ;; + remote.*) + local pfx="${cur%.*}." + cur="${cur#*.}" + COMPREPLY=($(compgen -P "$pfx" -S . \ + -W "$(__git_remotes)" -- "$cur")) + return + ;; + esac + COMPREPLY=($(compgen -W " + apply.whitespace + core.fileMode + core.gitProxy + core.ignoreStat + core.preferSymlinkRefs + core.logAllRefUpdates + core.repositoryFormatVersion + core.sharedRepository + core.warnAmbiguousRefs + core.compression + core.legacyHeaders + i18n.commitEncoding + diff.color + color.diff + diff.renameLimit + diff.renames + pager.color + color.pager + status.color + color.status + log.showroot + show.difftree + showbranch.default + whatchanged.difftree + http.sslVerify + http.sslCert + http.sslKey + http.sslCAInfo + http.sslCAPath + http.maxRequests + http.lowSpeedLimit http.lowSpeedTime + http.noEPSV + pack.window + repack.useDeltaBaseOffset + pull.octopus pull.twohead + merge.summary + receive.unpackLimit + receive.denyNonFastForwards + user.name user.email + tar.umask + gitcvs.enabled + gitcvs.logfile + branch. remote. + " -- "$cur")) +} + _git_reset () { local cur="${COMP_WORDS[COMP_CWORD]}" @@ -308,7 +755,19 @@ _git_reset () _git_show () { local cur="${COMP_WORDS[COMP_CWORD]}" - COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) + case "$cur" in + --pretty=*) + COMPREPLY=($(compgen -W " + oneline short medium full fuller email raw + " -- "${cur##--pretty=}")) + return + ;; + --*) + COMPREPLY=($(compgen -W "--pretty=" -- "$cur")) + return + ;; + esac + __git_complete_file } _git () @@ -327,11 +786,11 @@ _git () done if [ $c -eq $COMP_CWORD -a -z "$command" ]; then - COMPREPLY=($(compgen \ - -W "--git-dir= --version \ - $(git help -a|egrep '^ ') \ - $(__git_aliases)" \ - -- "${COMP_WORDS[COMP_CWORD]}")) + COMPREPLY=($(compgen -W " + --git-dir= --version --exec-path + $(__git_commands) + $(__git_aliases) + " -- "${COMP_WORDS[COMP_CWORD]}")) return; fi @@ -339,18 +798,27 @@ _git () [ "$expansion" ] && command="$expansion" case "$command" in + am) _git_am ;; + apply) _git_apply ;; branch) _git_branch ;; cat-file) _git_cat_file ;; checkout) _git_checkout ;; + cherry-pick) _git_cherry_pick ;; + commit) _git_commit ;; diff) _git_diff ;; diff-tree) _git_diff_tree ;; fetch) _git_fetch ;; + format-patch) _git_format_patch ;; log) _git_log ;; ls-remote) _git_ls_remote ;; ls-tree) _git_ls_tree ;; + merge) _git_merge;; merge-base) _git_merge_base ;; + name-rev) _git_name_rev ;; pull) _git_pull ;; push) _git_push ;; + rebase) _git_rebase ;; + repo-config) _git_repo_config ;; reset) _git_reset ;; show) _git_show ;; show-branch) _git_log ;; @@ -367,20 +835,29 @@ _gitk () complete -o default -o nospace -F _git git complete -o default -F _gitk gitk +complete -o default -F _git_am git-am +complete -o default -F _git_apply git-apply complete -o default -F _git_branch git-branch complete -o default -o nospace -F _git_cat_file git-cat-file complete -o default -F _git_checkout git-checkout +complete -o default -F _git_cherry_pick git-cherry-pick +complete -o default -F _git_commit git-commit complete -o default -o nospace -F _git_diff git-diff complete -o default -F _git_diff_tree git-diff-tree complete -o default -o nospace -F _git_fetch git-fetch +complete -o default -o nospace -F _git_format_patch git-format-patch complete -o default -o nospace -F _git_log git-log complete -o default -F _git_ls_remote git-ls-remote complete -o default -o nospace -F _git_ls_tree git-ls-tree +complete -o default -F _git_merge git-merge complete -o default -F _git_merge_base git-merge-base +complete -o default -F _git_name_rev git-name-rev complete -o default -o nospace -F _git_pull git-pull complete -o default -o nospace -F _git_push git-push +complete -o default -F _git_rebase git-rebase +complete -o default -F _git_repo_config git-repo-config complete -o default -F _git_reset git-reset -complete -o default -F _git_show git-show +complete -o default -o nospace -F _git_show git-show complete -o default -o nospace -F _git_log git-show-branch complete -o default -o nospace -F _git_log git-whatchanged @@ -389,15 +866,20 @@ complete -o default -o nospace -F _git_log git-whatchanged # included the '.exe' suffix. # if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then +complete -o default -F _git_apply git-apply.exe complete -o default -o nospace -F _git git.exe complete -o default -F _git_branch git-branch.exe complete -o default -o nospace -F _git_cat_file git-cat-file.exe complete -o default -o nospace -F _git_diff git-diff.exe complete -o default -o nospace -F _git_diff_tree git-diff-tree.exe +complete -o default -o nospace -F _git_format_patch git-format-patch.exe complete -o default -o nospace -F _git_log git-log.exe complete -o default -o nospace -F _git_ls_tree git-ls-tree.exe complete -o default -F _git_merge_base git-merge-base.exe +complete -o default -F _git_name_rev git-name-rev.exe complete -o default -o nospace -F _git_push git-push.exe +complete -o default -F _git_repo_config git-repo-config +complete -o default -o nospace -F _git_show git-show.exe complete -o default -o nospace -F _git_log git-show-branch.exe complete -o default -o nospace -F _git_log git-whatchanged.exe fi diff --git a/contrib/emacs/vc-git.el b/contrib/emacs/vc-git.el index 8b6361922f..3eb4bd19e9 100644 --- a/contrib/emacs/vc-git.el +++ b/contrib/emacs/vc-git.el @@ -58,8 +58,9 @@ (with-temp-buffer (let* ((dir (file-name-directory file)) (name (file-relative-name file dir))) - (when dir (cd dir)) - (and (ignore-errors (eq 0 (call-process "git" nil '(t nil) nil "ls-files" "-c" "-z" "--" name))) + (and (ignore-errors + (when dir (cd dir)) + (eq 0 (call-process "git" nil '(t nil) nil "ls-files" "-c" "-z" "--" name))) (let ((str (buffer-string))) (and (> (length str) (length name)) (string= (substring str 0 (1+ (length name))) (concat name "\0")))))))) diff --git a/contrib/mailmap.linux b/contrib/mailmap.linux new file mode 100644 index 0000000000..e4907f80f1 --- /dev/null +++ b/contrib/mailmap.linux @@ -0,0 +1,42 @@ +# +# Even with git, we don't always have name translations. +# So have an email->real name table to translate the +# (hopefully few) missing names +# +# repo-abbrev: /pub/scm/linux/kernel/git/ +# +Adrian Bunk <bunk@stusta.de> +Andreas Herrmann <aherrman@de.ibm.com> +Andrew Morton <akpm@osdl.org> +Andrew Vasquez <andrew.vasquez@qlogic.com> +Christoph Hellwig <hch@lst.de> +Corey Minyard <minyard@acm.org> +David Woodhouse <dwmw2@shinybook.infradead.org> +Domen Puncer <domen@coderock.org> +Douglas Gilbert <dougg@torque.net> +Ed L Cashin <ecashin@coraid.com> +Evgeniy Polyakov <johnpol@2ka.mipt.ru> +Felix Moeller <felix@derklecks.de> +Frank Zago <fzago@systemfabricworks.com> +Greg Kroah-Hartman <gregkh@suse.de> +James Bottomley <jejb@mulgrave.(none)> +James Bottomley <jejb@titanic.il.steeleye.com> +Jeff Garzik <jgarzik@pretzel.yyz.us> +Jens Axboe <axboe@suse.de> +Kay Sievers <kay.sievers@vrfy.org> +Mitesh shah <mshah@teja.com> +Morten Welinder <terra@gnome.org> +Morten Welinder <welinder@anemone.rentec.com> +Morten Welinder <welinder@darter.rentec.com> +Morten Welinder <welinder@troll.com> +Nguyen Anh Quynh <aquynh@gmail.com> +Paolo 'Blaisorblade' Giarrusso <blaisorblade@yahoo.it> +Peter A Jonsson <pj@ludd.ltu.se> +Ralf Wildenhues <Ralf.Wildenhues@gmx.de> +Rudolf Marek <R.Marek@sh.cvut.cz> +Rui Saraiva <rmps@joel.ist.utl.pt> +Sachin P Sant <ssant@in.ibm.com> +Santtu Hyrkk,Av(B <santtu.hyrkko@gmail.com> +Simon Kelley <simon@thekelleys.org.uk> +Tejun Heo <htejun@gmail.com> +Tony Luck <tony.luck@intel.com> diff --git a/contrib/vim/syntax/gitcommit.vim b/contrib/vim/syntax/gitcommit.vim index a9de09fa2f..d911efbb4b 100644 --- a/contrib/vim/syntax/gitcommit.vim +++ b/contrib/vim/syntax/gitcommit.vim @@ -1,7 +1,7 @@ syn region gitLine start=/^#/ end=/$/ -syn region gitCommit start=/^# Updated but not checked in:$/ end=/^#$/ contains=gitHead,gitCommitFile +syn region gitCommit start=/^# Added but not yet committed:$/ end=/^#$/ contains=gitHead,gitCommitFile syn region gitHead contained start=/^# (.*)/ end=/^#$/ -syn region gitChanged start=/^# Changed but not updated:/ end=/^#$/ contains=gitHead,gitChangedFile +syn region gitChanged start=/^# Changed but not added:/ end=/^#$/ contains=gitHead,gitChangedFile syn region gitUntracked start=/^# Untracked files:/ end=/^#$/ contains=gitHead,gitUntrackedFile syn match gitCommitFile contained /^#\t.*/hs=s+2 diff --git a/convert-objects.c b/convert-objects.c index 881258311a..a630132985 100644 --- a/convert-objects.c +++ b/convert-objects.c @@ -1,7 +1,3 @@ -#define _XOPEN_SOURCE 600 /* glibc2 and AIX 5.3L need 500, OpenBSD needs 600 for S_ISLNK() */ -#define _XOPEN_SOURCE_EXTENDED 1 /* AIX 5.3L needs this */ -#define _GNU_SOURCE -#include <time.h> #include "cache.h" #include "blob.h" #include "commit.h" @@ -1,20 +1,10 @@ -#include <signal.h> -#include <sys/wait.h> -#include <sys/socket.h> -#include <sys/time.h> -#include <sys/poll.h> -#include <netdb.h> -#include <netinet/in.h> -#include <arpa/inet.h> -#include <syslog.h> -#include <pwd.h> -#include <grp.h> -#include <limits.h> -#include "pkt-line.h" #include "cache.h" +#include "pkt-line.h" #include "exec_cmd.h" #include "interpolate.h" +#include <syslog.h> + #ifndef HOST_NAME_MAX #define HOST_NAME_MAX 256 #endif @@ -4,9 +4,6 @@ * Copyright (C) Linus Torvalds, 2005 */ -#include <time.h> -#include <sys/time.h> - #include "cache.h" static time_t my_mktime(struct tm *tm) diff --git a/diff-delta.c b/diff-delta.c index fa16d06c8d..9f998d0a73 100644 --- a/diff-delta.c +++ b/diff-delta.c @@ -18,11 +18,8 @@ * licensing gets turned into GPLv2 within this project. */ -#include <stdlib.h> -#include <string.h> -#include "delta.h" - #include "git-compat-util.h" +#include "delta.h" /* maximum hash entry list for the same hash bucket */ #define HASH_LIMIT 64 @@ -1,9 +1,6 @@ /* * Copyright (C) 2005 Junio C Hamano */ -#include <sys/types.h> -#include <sys/wait.h> -#include <signal.h> #include "cache.h" #include "quote.h" #include "diff.h" @@ -12,6 +9,12 @@ #include "xdiff-interface.h" #include "color.h" +#ifdef NO_FAST_WORKING_DIRECTORY +#define FAST_WORKING_DIRECTORY 0 +#else +#define FAST_WORKING_DIRECTORY 1 +#endif + static int use_size_cache; static int diff_detect_rename_default; @@ -60,7 +63,7 @@ int git_diff_ui_config(const char *var, const char *value) diff_rename_limit_default = git_config_int(var, value); return 0; } - if (!strcmp(var, "diff.color")) { + if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) { diff_use_color_default = git_config_colorbool(var, value); return 0; } @@ -74,7 +77,7 @@ int git_diff_ui_config(const char *var, const char *value) diff_detect_rename_default = DIFF_DETECT_RENAME; return 0; } - if (!strncmp(var, "diff.color.", 11)) { + if (!strncmp(var, "diff.color.", 11) || !strncmp(var, "color.diff.", 11)) { int slot = parse_diff_color_slot(var, 11); color_parse(value, var, diff_colors[slot]); return 0; @@ -795,6 +798,35 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options) set, total_files, adds, dels, reset); } +static void show_shortstats(struct diffstat_t* data) +{ + int i, adds = 0, dels = 0, total_files = data->nr; + + if (data->nr == 0) + return; + + for (i = 0; i < data->nr; i++) { + if (!data->files[i]->is_binary && + !data->files[i]->is_unmerged) { + int added = data->files[i]->added; + int deleted= data->files[i]->deleted; + if (!data->files[i]->is_renamed && + (added + deleted == 0)) { + total_files--; + } else { + adds += added; + dels += deleted; + } + } + free(data->files[i]->name); + free(data->files[i]); + } + free(data->files); + + printf(" %d files changed, %d insertions(+), %d deletions(-)\n", + total_files, adds, dels); +} + static void show_numstat(struct diffstat_t* data, struct diff_options *options) { int i; @@ -802,7 +834,10 @@ static void show_numstat(struct diffstat_t* data, struct diff_options *options) for (i = 0; i < data->nr; i++) { struct diffstat_file *file = data->files[i]; - printf("%d\t%d\t", file->added, file->deleted); + if (file->is_binary) + printf("-\t-\t"); + else + printf("%d\t%d\t", file->added, file->deleted); if (options->line_termination && quote_c_style(file->name, NULL, NULL, 0)) quote_c_style(file->name, NULL, stdout, 0); @@ -825,8 +860,6 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) if (line[0] == '+') { int i, spaces = 0; - data->lineno++; - /* check space before tab */ for (i = 1; i < len && (line[i] == ' ' || line[i] == '\t'); i++) if (line[i] == ' ') @@ -841,6 +874,8 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) if (isspace(line[len - 1])) printf("%s:%d: white space at end: %.*s\n", data->filename, data->lineno, (int)len, line); + + data->lineno++; } else if (line[0] == ' ') data->lineno++; else if (line[0] == '@') { @@ -1155,7 +1190,7 @@ void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1, * the work tree has that object contents, return true, so that * prepare_temp_file() does not have to inflate and extract. */ -static int work_tree_matches(const char *name, const unsigned char *sha1) +static int reuse_worktree_file(const char *name, const unsigned char *sha1, int want_file) { struct cache_entry *ce; struct stat st; @@ -1176,6 +1211,18 @@ static int work_tree_matches(const char *name, const unsigned char *sha1) if (!active_cache) return 0; + /* We want to avoid the working directory if our caller + * doesn't need the data in a normal file, this system + * is rather slow with its stat/open/mmap/close syscalls, + * and the object is contained in a pack file. The pack + * is probably already open and will be faster to obtain + * the data through than the working directory. Loose + * objects however would tend to be slower as they need + * to be individually opened and inflated. + */ + if (!FAST_WORKING_DIRECTORY && !want_file && has_sha1_pack(sha1, NULL)) + return 0; + len = strlen(name); pos = cache_name_pos(name, len); if (pos < 0) @@ -1262,7 +1309,7 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only) if (s->data) return err; if (!s->sha1_valid || - work_tree_matches(s->path, s->sha1)) { + reuse_worktree_file(s->path, s->sha1, 0)) { struct stat st; int fd; if (lstat(s->path, &st) < 0) { @@ -1369,7 +1416,7 @@ static void prepare_temp_file(const char *name, } if (!one->sha1_valid || - work_tree_matches(name, one->sha1)) { + reuse_worktree_file(name, one->sha1, 1)) { struct stat st; if (lstat(name, &st) < 0) { if (errno == ENOENT) @@ -1750,6 +1797,7 @@ int diff_setup_done(struct diff_options *options) options->output_format &= ~(DIFF_FORMAT_RAW | DIFF_FORMAT_NUMSTAT | DIFF_FORMAT_DIFFSTAT | + DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_PATCH); @@ -1760,6 +1808,7 @@ int diff_setup_done(struct diff_options *options) if (options->output_format & (DIFF_FORMAT_PATCH | DIFF_FORMAT_NUMSTAT | DIFF_FORMAT_DIFFSTAT | + DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_CHECKDIFF)) options->recursive = 1; @@ -1851,6 +1900,9 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) else if (!strcmp(arg, "--numstat")) { options->output_format |= DIFF_FORMAT_NUMSTAT; } + else if (!strcmp(arg, "--shortstat")) { + options->output_format |= DIFF_FORMAT_SHORTSTAT; + } else if (!strncmp(arg, "--stat", 6)) { char *end; int width = options->stat_width; @@ -2625,7 +2677,7 @@ void diff_flush(struct diff_options *options) separator++; } - if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_NUMSTAT)) { + if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT)) { struct diffstat_t diffstat; memset(&diffstat, 0, sizeof(struct diffstat_t)); @@ -2639,6 +2691,8 @@ void diff_flush(struct diff_options *options) show_numstat(&diffstat, options); if (output_format & DIFF_FORMAT_DIFFSTAT) show_stats(&diffstat, options); + else if (output_format & DIFF_FORMAT_SHORTSTAT) + show_shortstats(&diffstat); separator++; } @@ -29,6 +29,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_FORMAT_NUMSTAT 0x0004 #define DIFF_FORMAT_SUMMARY 0x0008 #define DIFF_FORMAT_PATCH 0x0010 +#define DIFF_FORMAT_SHORTSTAT 0x0020 /* These override all above */ #define DIFF_FORMAT_NAME 0x0100 diff --git a/diffcore-order.c b/diffcore-order.c index aef6da6044..7ad0946185 100644 --- a/diffcore-order.c +++ b/diffcore-order.c @@ -4,7 +4,6 @@ #include "cache.h" #include "diff.h" #include "diffcore.h" -#include <fnmatch.h> static char **order; static int order_cnt; diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c index cfcce315ba..de44adabf0 100644 --- a/diffcore-pickaxe.c +++ b/diffcore-pickaxe.c @@ -5,8 +5,6 @@ #include "diff.h" #include "diffcore.h" -#include <regex.h> - static unsigned int contains(struct diff_filespec *one, const char *needle, unsigned long len, regex_t *regexp) diff --git a/diffcore-rename.c b/diffcore-rename.c index 57a74b6bb8..91fa2bea51 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -109,6 +109,8 @@ static int is_exact_match(struct diff_filespec *src, return 0; if (src->size != dst->size) return 0; + if (src->sha1_valid && dst->sha1_valid) + return !hashcmp(src->sha1, dst->sha1); if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0)) return 0; if (src->size == dst->size && @@ -5,9 +5,6 @@ * Copyright (C) Linus Torvalds, 2005-2006 * Junio Hamano, 2005-2006 */ -#include <dirent.h> -#include <fnmatch.h> - #include "cache.h" #include "dir.h" @@ -43,6 +40,18 @@ int common_prefix(const char **pathspec) return prefix; } +/* + * Does 'match' matches the given name? + * A match is found if + * + * (1) the 'match' string is leading directory of 'name', or + * (2) the 'match' string is a wildcard and matches 'name', or + * (3) the 'match' string is exactly the same as 'name'. + * + * and the return value tells which case it was. + * + * It returns 0 when there is no match. + */ static int match_one(const char *match, const char *name, int namelen) { int matchlen; @@ -50,27 +59,30 @@ static int match_one(const char *match, const char *name, int namelen) /* If the match was just the prefix, we matched */ matchlen = strlen(match); if (!matchlen) - return 1; + return MATCHED_RECURSIVELY; /* * If we don't match the matchstring exactly, * we need to match by fnmatch */ if (strncmp(match, name, matchlen)) - return !fnmatch(match, name, 0); + return !fnmatch(match, name, 0) ? MATCHED_FNMATCH : 0; - /* - * If we did match the string exactly, we still - * need to make sure that it happened on a path - * component boundary (ie either the last character - * of the match was '/', or the next character of - * the name was '/' or the terminating NUL. - */ - return match[matchlen-1] == '/' || - name[matchlen] == '/' || - !name[matchlen]; + if (!name[matchlen]) + return MATCHED_EXACTLY; + if (match[matchlen-1] == '/' || name[matchlen] == '/') + return MATCHED_RECURSIVELY; + return 0; } +/* + * Given a name and a list of pathspecs, see if the name matches + * any of the pathspecs. The caller is also interested in seeing + * all pathspec matches some names it calls this function with + * (otherwise the user could have mistyped the unmatched pathspec), + * and a mark is left in seen[] array for pathspec element that + * actually matched anything. + */ int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen) { int retval; @@ -80,12 +92,16 @@ int match_pathspec(const char **pathspec, const char *name, int namelen, int pre namelen -= prefix; for (retval = 0; (match = *pathspec++) != NULL; seen++) { - if (retval & *seen) + int how; + if (retval && *seen == MATCHED_EXACTLY) continue; match += prefix; - if (match_one(match, name, namelen)) { - retval = 1; - *seen = 1; + how = match_one(match, name, namelen); + if (how) { + if (retval < how) + retval = how; + if (*seen < how) + *seen = how; } } return retval; @@ -156,7 +172,7 @@ void add_excludes_from_file(struct dir_struct *dir, const char *fname) die("cannot use %s as an exclude file", fname); } -static int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen) +int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen) { char exclude_file[PATH_MAX]; struct exclude_list *el = &dir->exclude_list[EXC_DIRS]; @@ -170,7 +186,7 @@ static int push_exclude_per_directory(struct dir_struct *dir, const char *base, return current_nr; } -static void pop_exclude_per_directory(struct dir_struct *dir, int stk) +void pop_exclude_per_directory(struct dir_struct *dir, int stk) { struct exclude_list *el = &dir->exclude_list[EXC_DIRS]; @@ -244,7 +260,8 @@ int excluded(struct dir_struct *dir, const char *pathname) return 0; } -static void add_name(struct dir_struct *dir, const char *pathname, int len) +static void add_name(struct dir_struct *dir, const char *pathname, int len, + int ignored_entry) { struct dir_entry *ent; @@ -257,6 +274,7 @@ static void add_name(struct dir_struct *dir, const char *pathname, int len) dir->entries = xrealloc(dir->entries, alloc*sizeof(ent)); } ent = xmalloc(sizeof(*ent) + len + 1); + ent->ignored_entry = ignored_entry; ent->len = len; memcpy(ent->name, pathname, len); ent->name[len] = 0; @@ -298,6 +316,7 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co while ((de = readdir(fdir)) != NULL) { int len; + int ignored_entry; if ((de->d_name[0] == '.') && (de->d_name[1] == 0 || @@ -306,11 +325,12 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co continue; len = strlen(de->d_name); memcpy(fullname + baselen, de->d_name, len+1); - if (excluded(dir, fullname) != dir->show_ignored) { - if (!dir->show_ignored || DTYPE(de) != DT_DIR) { - continue; - } - } + ignored_entry = excluded(dir, fullname); + + if (!dir->show_both && + (ignored_entry != dir->show_ignored) && + (!dir->show_ignored || DTYPE(de) != DT_DIR)) + continue; switch (DTYPE(de)) { struct stat st; @@ -348,7 +368,8 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co if (check_only) goto exit_early; else - add_name(dir, fullname, baselen + len); + add_name(dir, fullname, baselen + len, + ignored_entry); } exit_early: closedir(fdir); @@ -13,7 +13,8 @@ struct dir_entry { - int len; + unsigned ignored_entry : 1; + unsigned int len : 15; char name[FLEX_ARRAY]; /* more */ }; @@ -29,7 +30,8 @@ struct exclude_list { struct dir_struct { int nr, alloc; - unsigned int show_ignored:1, + unsigned int show_both: 1, + show_ignored:1, show_other_directories:1, hide_empty_directories:1; struct dir_entry **entries; @@ -40,9 +42,16 @@ struct dir_struct { }; extern int common_prefix(const char **pathspec); + +#define MATCHED_RECURSIVELY 1 +#define MATCHED_FNMATCH 2 +#define MATCHED_EXACTLY 3 extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen); extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen); +extern int push_exclude_per_directory(struct dir_struct *, const char *, int); +extern void pop_exclude_per_directory(struct dir_struct *, int); + extern int excluded(struct dir_struct *, const char *); extern void add_excludes_from_file(struct dir_struct *, const char *fname); extern void add_exclude(const char *string, const char *base, @@ -1,5 +1,3 @@ -#include <sys/types.h> -#include <dirent.h> #include "cache.h" #include "blob.h" diff --git a/environment.c b/environment.c index 84d870ca4e..f8c7dbcead 100644 --- a/environment.c +++ b/environment.c @@ -48,6 +48,16 @@ static void setup_git_env(void) git_graft_file = getenv(GRAFT_ENVIRONMENT); if (!git_graft_file) git_graft_file = xstrdup(git_path("info/grafts")); + log_all_ref_updates = !is_bare_git_dir(git_dir); +} + +int is_bare_git_dir (const char *dir) +{ + const char *s; + if (!strcmp(dir, DEFAULT_GIT_DIR_ENVIRONMENT)) + return 0; + s = strrchr(dir, '/'); + return !s || strcmp(s + 1, DEFAULT_GIT_DIR_ENVIRONMENT); } const char *get_git_dir(void) diff --git a/exec_cmd.c b/exec_cmd.c index 5d6a1247b4..3996bce33f 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -21,7 +21,7 @@ const char *git_exec_path(void) if (current_exec_path) return current_exec_path; - env = getenv("GIT_EXEC_PATH"); + env = getenv(EXEC_PATH_ENVIRONMENT); if (env && *env) { return env; } @@ -35,7 +35,7 @@ int execv_git_cmd(const char **argv) char git_command[PATH_MAX + 1]; int i; const char *paths[] = { current_exec_path, - getenv("GIT_EXEC_PATH"), + getenv(EXEC_PATH_ENVIRONMENT), builtin_exec_path }; for (i = 0; i < ARRAY_SIZE(paths); ++i) { diff --git a/fetch-pack.c b/fetch-pack.c index 80979b83be..c527bf9e96 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -5,7 +5,6 @@ #include "tag.h" #include "exec_cmd.h" #include "sideband.h" -#include <sys/wait.h> static int keep_pack; static int quiet; @@ -603,6 +602,29 @@ static int fetch_pack(int fd[2], int nr_match, char **match) return 0; } +static int remove_duplicates(int nr_heads, char **heads) +{ + int src, dst; + + for (src = dst = 0; src < nr_heads; src++) { + /* If heads[src] is different from any of + * heads[0..dst], push it in. + */ + int i; + for (i = 0; i < dst; i++) { + if (!strcmp(heads[i], heads[src])) + break; + } + if (i < dst) + continue; + if (src != dst) + heads[dst] = heads[src]; + dst++; + } + heads[dst] = 0; + return dst; +} + int main(int argc, char **argv) { int i, ret, nr_heads; @@ -662,6 +684,8 @@ int main(int argc, char **argv) pid = git_connect(fd, dest, exec); if (pid < 0) return 1; + if (heads && nr_heads) + nr_heads = remove_duplicates(nr_heads, heads); ret = fetch_pack(fd, nr_heads, heads); close(fd[0]); close(fd[1]); @@ -1,6 +1,5 @@ -#include "fetch.h" - #include "cache.h" +#include "fetch.h" #include "commit.h" #include "tree.h" #include "tree-walk.h" @@ -22,14 +21,15 @@ void pull_say(const char *fmt, const char *hex) fprintf(stderr, fmt, hex); } -static void report_missing(const char *what, const unsigned char *missing) +static void report_missing(const struct object *obj) { char missing_hex[41]; - - strcpy(missing_hex, sha1_to_hex(missing));; - fprintf(stderr, - "Cannot obtain needed %s %s\nwhile processing commit %s.\n", - what, missing_hex, sha1_to_hex(current_commit_sha1)); + strcpy(missing_hex, sha1_to_hex(obj->sha1));; + fprintf(stderr, "Cannot obtain needed %s %s\n", + obj->type ? typename(obj->type): "object", missing_hex); + if (!is_null_sha1(current_commit_sha1)) + fprintf(stderr, "while processing commit %s.\n", + sha1_to_hex(current_commit_sha1)); } static int process(struct object *obj); @@ -177,7 +177,7 @@ static int loop(void) */ if (! (obj->flags & TO_SCAN)) { if (fetch(obj->sha1)) { - report_missing(typename(obj->type), obj->sha1); + report_missing(obj); return -1; } } diff --git a/fsck-objects.c b/fsck-objects.c index 46b628cb94..1cc3b399bc 100644 --- a/fsck-objects.c +++ b/fsck-objects.c @@ -1,6 +1,3 @@ -#include <sys/types.h> -#include <dirent.h> - #include "cache.h" #include "commit.h" #include "tree.h" @@ -402,6 +399,25 @@ static void fsck_dir(int i, char *path) static int default_refs; +static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1, char *datail, void *cb_data) +{ + struct object *obj; + + if (!is_null_sha1(osha1)) { + obj = lookup_object(osha1); + if (obj) { + obj->used = 1; + mark_reachable(obj, REACHABLE); + } + } + obj = lookup_object(nsha1); + if (obj) { + obj->used = 1; + mark_reachable(obj, REACHABLE); + } + return 0; +} + static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { struct object *obj; @@ -419,6 +435,9 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int f default_refs++; obj->used = 1; mark_reachable(obj, REACHABLE); + + for_each_reflog_ent(refname, fsck_handle_reflog_ent, NULL); + return 0; } diff --git a/generate-cmdlist.sh b/generate-cmdlist.sh index 5450918be3..06c42b042d 100755 --- a/generate-cmdlist.sh +++ b/generate-cmdlist.sh @@ -4,7 +4,7 @@ echo "/* Automatically generated by $0 */ struct cmdname_help { char name[16]; - char help[64]; + char help[80]; }; struct cmdname_help common_cmds[] = {" diff --git a/git-add--interactive.perl b/git-add--interactive.perl new file mode 100755 index 0000000000..0057f86588 --- /dev/null +++ b/git-add--interactive.perl @@ -0,0 +1,804 @@ +#!/usr/bin/perl -w + + +use strict; + +sub run_cmd_pipe { + my $fh = undef; + open($fh, '-|', @_) or die; + return <$fh>; +} + +my ($GIT_DIR) = run_cmd_pipe(qw(git rev-parse --git-dir)); + +if (!defined $GIT_DIR) { + exit(1); # rev-parse would have already said "not a git repo" +} +chomp($GIT_DIR); + +sub refresh { + my $fh; + open $fh, '-|', qw(git update-index --refresh) + or die; + while (<$fh>) { + ;# ignore 'needs update' + } + close $fh; +} + +sub list_untracked { + map { + chomp $_; + $_; + } + run_cmd_pipe(qw(git ls-files --others + --exclude-per-directory=.gitignore), + "--exclude-from=$GIT_DIR/info/exclude", + '--', @_); +} + +my $status_fmt = '%12s %12s %s'; +my $status_head = sprintf($status_fmt, 'staged', 'unstaged', 'path'); + +# Returns list of hashes, contents of each of which are: +# PRINT: print message +# VALUE: pathname +# BINARY: is a binary path +# INDEX: is index different from HEAD? +# FILE: is file different from index? +# INDEX_ADDDEL: is it add/delete between HEAD and index? +# FILE_ADDDEL: is it add/delete between index and file? + +sub list_modified { + my ($only) = @_; + my (%data, @return); + my ($add, $del, $adddel, $file); + + for (run_cmd_pipe(qw(git diff-index --cached + --numstat --summary HEAD))) { + if (($add, $del, $file) = + /^([-\d]+) ([-\d]+) (.*)/) { + my ($change, $bin); + if ($add eq '-' && $del eq '-') { + $change = 'binary'; + $bin = 1; + } + else { + $change = "+$add/-$del"; + } + $data{$file} = { + INDEX => $change, + BINARY => $bin, + FILE => 'nothing', + } + } + elsif (($adddel, $file) = + /^ (create|delete) mode [0-7]+ (.*)$/) { + $data{$file}{INDEX_ADDDEL} = $adddel; + } + } + + for (run_cmd_pipe(qw(git diff-files --numstat --summary))) { + if (($add, $del, $file) = + /^([-\d]+) ([-\d]+) (.*)/) { + if (!exists $data{$file}) { + $data{$file} = +{ + INDEX => 'unchanged', + BINARY => 0, + }; + } + my ($change, $bin); + if ($add eq '-' && $del eq '-') { + $change = 'binary'; + $bin = 1; + } + else { + $change = "+$add/-$del"; + } + $data{$file}{FILE} = $change; + if ($bin) { + $data{$file}{BINARY} = 1; + } + } + elsif (($adddel, $file) = + /^ (create|delete) mode [0-7]+ (.*)$/) { + $data{$file}{FILE_ADDDEL} = $adddel; + } + } + + for (sort keys %data) { + my $it = $data{$_}; + + if ($only) { + if ($only eq 'index-only') { + next if ($it->{INDEX} eq 'unchanged'); + } + if ($only eq 'file-only') { + next if ($it->{FILE} eq 'nothing'); + } + } + push @return, +{ + VALUE => $_, + PRINT => (sprintf $status_fmt, + $it->{INDEX}, $it->{FILE}, $_), + %$it, + }; + } + return @return; +} + +sub find_unique { + my ($string, @stuff) = @_; + my $found = undef; + for (my $i = 0; $i < @stuff; $i++) { + my $it = $stuff[$i]; + my $hit = undef; + if (ref $it) { + if ((ref $it) eq 'ARRAY') { + $it = $it->[0]; + } + else { + $it = $it->{VALUE}; + } + } + eval { + if ($it =~ /^$string/) { + $hit = 1; + }; + }; + if (defined $hit && defined $found) { + return undef; + } + if ($hit) { + $found = $i + 1; + } + } + return $found; +} + +sub list_and_choose { + my ($opts, @stuff) = @_; + my (@chosen, @return); + my $i; + + TOPLOOP: + while (1) { + my $last_lf = 0; + + if ($opts->{HEADER}) { + if (!$opts->{LIST_FLAT}) { + print " "; + } + print "$opts->{HEADER}\n"; + } + for ($i = 0; $i < @stuff; $i++) { + my $chosen = $chosen[$i] ? '*' : ' '; + my $print = $stuff[$i]; + if (ref $print) { + if ((ref $print) eq 'ARRAY') { + $print = $print->[0]; + } + else { + $print = $print->{PRINT}; + } + } + printf("%s%2d: %s", $chosen, $i+1, $print); + if (($opts->{LIST_FLAT}) && + (($i + 1) % ($opts->{LIST_FLAT}))) { + print "\t"; + $last_lf = 0; + } + else { + print "\n"; + $last_lf = 1; + } + } + if (!$last_lf) { + print "\n"; + } + + return if ($opts->{LIST_ONLY}); + + print $opts->{PROMPT}; + if ($opts->{SINGLETON}) { + print "> "; + } + else { + print ">> "; + } + my $line = <STDIN>; + last if (!$line); + chomp $line; + my $donesomething = 0; + for my $choice (split(/[\s,]+/, $line)) { + my $choose = 1; + my ($bottom, $top); + + # Input that begins with '-'; unchoose + if ($choice =~ s/^-//) { + $choose = 0; + } + # A range can be specified like 5-7 + if ($choice =~ /^(\d+)-(\d+)$/) { + ($bottom, $top) = ($1, $2); + } + elsif ($choice =~ /^\d+$/) { + $bottom = $top = $choice; + } + elsif ($choice eq '*') { + $bottom = 1; + $top = 1 + @stuff; + } + else { + $bottom = $top = find_unique($choice, @stuff); + if (!defined $bottom) { + print "Huh ($choice)?\n"; + next TOPLOOP; + } + } + if ($opts->{SINGLETON} && $bottom != $top) { + print "Huh ($choice)?\n"; + next TOPLOOP; + } + for ($i = $bottom-1; $i <= $top-1; $i++) { + next if (@stuff <= $i); + $chosen[$i] = $choose; + $donesomething++; + } + } + last if (!$donesomething || $opts->{IMMEDIATE}); + } + for ($i = 0; $i < @stuff; $i++) { + if ($chosen[$i]) { + push @return, $stuff[$i]; + } + } + return @return; +} + +sub status_cmd { + list_and_choose({ LIST_ONLY => 1, HEADER => $status_head }, + list_modified()); + print "\n"; +} + +sub say_n_paths { + my $did = shift @_; + my $cnt = scalar @_; + print "$did "; + if (1 < $cnt) { + print "$cnt paths\n"; + } + else { + print "one path\n"; + } +} + +sub update_cmd { + my @mods = list_modified('file-only'); + return if (!@mods); + + my @update = list_and_choose({ PROMPT => 'Update', + HEADER => $status_head, }, + @mods); + if (@update) { + system(qw(git update-index --add --), + map { $_->{VALUE} } @update); + say_n_paths('updated', @update); + } + print "\n"; +} + +sub revert_cmd { + my @update = list_and_choose({ PROMPT => 'Revert', + HEADER => $status_head, }, + list_modified()); + if (@update) { + my @lines = run_cmd_pipe(qw(git ls-tree HEAD --), + map { $_->{VALUE} } @update); + my $fh; + open $fh, '|-', qw(git update-index --index-info) + or die; + for (@lines) { + print $fh $_; + } + close($fh); + for (@update) { + if ($_->{INDEX_ADDDEL} && + $_->{INDEX_ADDDEL} eq 'create') { + system(qw(git update-index --force-remove --), + $_->{VALUE}); + print "note: $_->{VALUE} is untracked now.\n"; + } + } + refresh(); + say_n_paths('reverted', @update); + } + print "\n"; +} + +sub add_untracked_cmd { + my @add = list_and_choose({ PROMPT => 'Add untracked' }, + list_untracked()); + if (@add) { + system(qw(git update-index --add --), @add); + say_n_paths('added', @add); + } + print "\n"; +} + +sub parse_diff { + my ($path) = @_; + my @diff = run_cmd_pipe(qw(git diff-files -p --), $path); + my (@hunk) = { TEXT => [] }; + + for (@diff) { + if (/^@@ /) { + push @hunk, { TEXT => [] }; + } + push @{$hunk[-1]{TEXT}}, $_; + } + return @hunk; +} + +sub hunk_splittable { + my ($text) = @_; + + my @s = split_hunk($text); + return (1 < @s); +} + +sub parse_hunk_header { + my ($line) = @_; + my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = + $line =~ /^@@ -(\d+)(?:,(\d+)) \+(\d+)(?:,(\d+)) @@/; + return ($o_ofs, $o_cnt, $n_ofs, $n_cnt); +} + +sub split_hunk { + my ($text) = @_; + my @split = (); + + # If there are context lines in the middle of a hunk, + # it can be split, but we would need to take care of + # overlaps later. + + my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = parse_hunk_header($text->[0]); + my $hunk_start = 1; + my $next_hunk_start; + + OUTER: + while (1) { + my $next_hunk_start = undef; + my $i = $hunk_start - 1; + my $this = +{ + TEXT => [], + OLD => $o_ofs, + NEW => $n_ofs, + OCNT => 0, + NCNT => 0, + ADDDEL => 0, + POSTCTX => 0, + }; + + while (++$i < @$text) { + my $line = $text->[$i]; + if ($line =~ /^ /) { + if ($this->{ADDDEL} && + !defined $next_hunk_start) { + # We have seen leading context and + # adds/dels and then here is another + # context, which is trailing for this + # split hunk and leading for the next + # one. + $next_hunk_start = $i; + } + push @{$this->{TEXT}}, $line; + $this->{OCNT}++; + $this->{NCNT}++; + if (defined $next_hunk_start) { + $this->{POSTCTX}++; + } + next; + } + + # add/del + if (defined $next_hunk_start) { + # We are done with the current hunk and + # this is the first real change for the + # next split one. + $hunk_start = $next_hunk_start; + $o_ofs = $this->{OLD} + $this->{OCNT}; + $n_ofs = $this->{NEW} + $this->{NCNT}; + $o_ofs -= $this->{POSTCTX}; + $n_ofs -= $this->{POSTCTX}; + push @split, $this; + redo OUTER; + } + push @{$this->{TEXT}}, $line; + $this->{ADDDEL}++; + if ($line =~ /^-/) { + $this->{OCNT}++; + } + else { + $this->{NCNT}++; + } + } + + push @split, $this; + last; + } + + for my $hunk (@split) { + $o_ofs = $hunk->{OLD}; + $n_ofs = $hunk->{NEW}; + $o_cnt = $hunk->{OCNT}; + $n_cnt = $hunk->{NCNT}; + + my $head = ("@@ -$o_ofs" . + (($o_cnt != 1) ? ",$o_cnt" : '') . + " +$n_ofs" . + (($n_cnt != 1) ? ",$n_cnt" : '') . + " @@\n"); + unshift @{$hunk->{TEXT}}, $head; + } + return map { $_->{TEXT} } @split; +} + +sub find_last_o_ctx { + my ($it) = @_; + my $text = $it->{TEXT}; + my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = parse_hunk_header($text->[0]); + my $i = @{$text}; + my $last_o_ctx = $o_ofs + $o_cnt; + while (0 < --$i) { + my $line = $text->[$i]; + if ($line =~ /^ /) { + $last_o_ctx--; + next; + } + last; + } + return $last_o_ctx; +} + +sub merge_hunk { + my ($prev, $this) = @_; + my ($o0_ofs, $o0_cnt, $n0_ofs, $n0_cnt) = + parse_hunk_header($prev->{TEXT}[0]); + my ($o1_ofs, $o1_cnt, $n1_ofs, $n1_cnt) = + parse_hunk_header($this->{TEXT}[0]); + + my (@line, $i, $ofs, $o_cnt, $n_cnt); + $ofs = $o0_ofs; + $o_cnt = $n_cnt = 0; + for ($i = 1; $i < @{$prev->{TEXT}}; $i++) { + my $line = $prev->{TEXT}[$i]; + if ($line =~ /^\+/) { + $n_cnt++; + push @line, $line; + next; + } + + last if ($o1_ofs <= $ofs); + + $o_cnt++; + $ofs++; + if ($line =~ /^ /) { + $n_cnt++; + } + push @line, $line; + } + + for ($i = 1; $i < @{$this->{TEXT}}; $i++) { + my $line = $this->{TEXT}[$i]; + if ($line =~ /^\+/) { + $n_cnt++; + push @line, $line; + next; + } + $ofs++; + $o_cnt++; + if ($line =~ /^ /) { + $n_cnt++; + } + push @line, $line; + } + my $head = ("@@ -$o0_ofs" . + (($o_cnt != 1) ? ",$o_cnt" : '') . + " +$n0_ofs" . + (($n_cnt != 1) ? ",$n_cnt" : '') . + " @@\n"); + @{$prev->{TEXT}} = ($head, @line); +} + +sub coalesce_overlapping_hunks { + my (@in) = @_; + my @out = (); + + my ($last_o_ctx); + + for (grep { $_->{USE} } @in) { + my $text = $_->{TEXT}; + my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = + parse_hunk_header($text->[0]); + if (defined $last_o_ctx && + $o_ofs <= $last_o_ctx) { + merge_hunk($out[-1], $_); + } + else { + push @out, $_; + } + $last_o_ctx = find_last_o_ctx($out[-1]); + } + return @out; +} + +sub help_patch_cmd { + print <<\EOF ; +y - stage this hunk +n - do not stage this hunk +a - stage this and all the remaining hunks +d - do not stage this hunk nor any of the remaining hunks +j - leave this hunk undecided, see next undecided hunk +J - leave this hunk undecided, see next hunk +k - leave this hunk undecided, see previous undecided hunk +K - leave this hunk undecided, see previous hunk +s - split the current hunk into smaller hunks +EOF +} + +sub patch_update_cmd { + my @mods = list_modified('file-only'); + @mods = grep { !($_->{BINARY}) } @mods; + return if (!@mods); + + my ($it) = list_and_choose({ PROMPT => 'Patch update', + SINGLETON => 1, + IMMEDIATE => 1, + HEADER => $status_head, }, + @mods); + return if (!$it); + + my ($ix, $num); + my $path = $it->{VALUE}; + my ($head, @hunk) = parse_diff($path); + for (@{$head->{TEXT}}) { + print; + } + $num = scalar @hunk; + $ix = 0; + + while (1) { + my ($prev, $next, $other, $undecided, $i); + $other = ''; + + if ($num <= $ix) { + $ix = 0; + } + for ($i = 0; $i < $ix; $i++) { + if (!defined $hunk[$i]{USE}) { + $prev = 1; + $other .= '/k'; + last; + } + } + if ($ix) { + $other .= '/K'; + } + for ($i = $ix + 1; $i < $num; $i++) { + if (!defined $hunk[$i]{USE}) { + $next = 1; + $other .= '/j'; + last; + } + } + if ($ix < $num - 1) { + $other .= '/J'; + } + for ($i = 0; $i < $num; $i++) { + if (!defined $hunk[$i]{USE}) { + $undecided = 1; + last; + } + } + last if (!$undecided); + + if (hunk_splittable($hunk[$ix]{TEXT})) { + $other .= '/s'; + } + for (@{$hunk[$ix]{TEXT}}) { + print; + } + print "Stage this hunk [y/n/a/d$other/?]? "; + my $line = <STDIN>; + if ($line) { + if ($line =~ /^y/i) { + $hunk[$ix]{USE} = 1; + } + elsif ($line =~ /^n/i) { + $hunk[$ix]{USE} = 0; + } + elsif ($line =~ /^a/i) { + while ($ix < $num) { + if (!defined $hunk[$ix]{USE}) { + $hunk[$ix]{USE} = 1; + } + $ix++; + } + next; + } + elsif ($line =~ /^d/i) { + while ($ix < $num) { + if (!defined $hunk[$ix]{USE}) { + $hunk[$ix]{USE} = 0; + } + $ix++; + } + next; + } + elsif ($other =~ /K/ && $line =~ /^K/) { + $ix--; + next; + } + elsif ($other =~ /J/ && $line =~ /^J/) { + $ix++; + next; + } + elsif ($other =~ /k/ && $line =~ /^k/) { + while (1) { + $ix--; + last if (!$ix || + !defined $hunk[$ix]{USE}); + } + next; + } + elsif ($other =~ /j/ && $line =~ /^j/) { + while (1) { + $ix++; + last if ($ix >= $num || + !defined $hunk[$ix]{USE}); + } + next; + } + elsif ($other =~ /s/ && $line =~ /^s/) { + my @split = split_hunk($hunk[$ix]{TEXT}); + if (1 < @split) { + print "Split into ", + scalar(@split), " hunks.\n"; + } + splice(@hunk, $ix, 1, + map { +{ TEXT => $_, USE => undef } } + @split); + $num = scalar @hunk; + next; + } + else { + help_patch_cmd($other); + next; + } + # soft increment + while (1) { + $ix++; + last if ($ix >= $num || + !defined $hunk[$ix]{USE}); + } + } + } + + @hunk = coalesce_overlapping_hunks(@hunk); + + my ($o_lofs, $n_lofs) = (0, 0); + my @result = (); + for (@hunk) { + my $text = $_->{TEXT}; + my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = + parse_hunk_header($text->[0]); + + if (!$_->{USE}) { + if (!defined $o_cnt) { $o_cnt = 1; } + if (!defined $n_cnt) { $n_cnt = 1; } + + # We would have added ($n_cnt - $o_cnt) lines + # to the postimage if we were to use this hunk, + # but we didn't. So the line number that the next + # hunk starts at would be shifted by that much. + $n_lofs -= ($n_cnt - $o_cnt); + next; + } + else { + if ($n_lofs) { + $n_ofs += $n_lofs; + $text->[0] = ("@@ -$o_ofs" . + ((defined $o_cnt) + ? ",$o_cnt" : '') . + " +$n_ofs" . + ((defined $n_cnt) + ? ",$n_cnt" : '') . + " @@\n"); + } + for (@$text) { + push @result, $_; + } + } + } + + if (@result) { + my $fh; + + open $fh, '|-', qw(git apply --cached); + for (@{$head->{TEXT}}, @result) { + print $fh $_; + } + if (!close $fh) { + for (@{$head->{TEXT}}, @result) { + print STDERR $_; + } + } + refresh(); + } + + print "\n"; +} + +sub diff_cmd { + my @mods = list_modified('index-only'); + @mods = grep { !($_->{BINARY}) } @mods; + return if (!@mods); + my (@them) = list_and_choose({ PROMPT => 'Review diff', + IMMEDIATE => 1, + HEADER => $status_head, }, + @mods); + return if (!@them); + system(qw(git diff-index -p --cached HEAD --), + map { $_->{VALUE} } @them); +} + +sub quit_cmd { + print "Bye.\n"; + exit(0); +} + +sub help_cmd { + print <<\EOF ; +status - show paths with changes +update - add working tree state to the staged set of changes +revert - revert staged set of changes back to the HEAD version +patch - pick hunks and update selectively +diff - view diff between HEAD and index +add untracked - add contents of untracked files to the staged set of changes +EOF +} + +sub main_loop { + my @cmd = ([ 'status', \&status_cmd, ], + [ 'update', \&update_cmd, ], + [ 'revert', \&revert_cmd, ], + [ 'add untracked', \&add_untracked_cmd, ], + [ 'patch', \&patch_update_cmd, ], + [ 'diff', \&diff_cmd, ], + [ 'quit', \&quit_cmd, ], + [ 'help', \&help_cmd, ], + ); + while (1) { + my ($it) = list_and_choose({ PROMPT => 'What now', + SINGLETON => 1, + LIST_FLAT => 4, + HEADER => '*** Commands ***', + IMMEDIATE => 1 }, @cmd); + if ($it) { + eval { + $it->[1]->(); + }; + if ($@) { + print "$@"; + } + } + } +} + +my @z; + +refresh(); +status_cmd(); +main_loop(); @@ -246,6 +246,10 @@ last=`cat "$dotest/last"` this=`cat "$dotest/next"` if test "$skip" = t then + if test -d "$GIT_DIR/rr-cache" + then + git-rerere clear + fi this=`expr "$this" + 1` resume= fi @@ -397,17 +401,21 @@ do changed="$(git-diff-index --cached --name-only HEAD)" if test '' = "$changed" then - echo "No changes - did you forget update-index?" + echo "No changes - did you forget to use 'git add'?" stop_here_user_resolve $this fi unmerged=$(git-ls-files -u) if test -n "$unmerged" then echo "You still have unmerged paths in your index" - echo "did you forget update-index?" + echo "did you forget to use 'git add'?" stop_here_user_resolve $this fi apply_status=0 + if test -d "$GIT_DIR/rr-cache" + then + git rerere + fi ;; esac diff --git a/git-checkout.sh b/git-checkout.sh index 737abd0c09..92ec069a3a 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -146,8 +146,11 @@ fi [ -z "$branch$newbranch" ] && [ "$new" != "$old" ] && - die "git checkout: to checkout the requested commit you need to specify - a name for a new branch which is created and switched to" + die "git checkout: provided reference cannot be checked out directly + + You need -b to associate a new branch with the wanted checkout. Example: + git checkout -b <new_branch_name> $arg +" if [ "X$old" = X ] then @@ -161,7 +164,7 @@ then git-read-tree --reset -u $new else git-update-index --refresh >/dev/null - merge_error=$(git-read-tree -m -u $old $new 2>&1) || ( + merge_error=$(git-read-tree -m -u --exclude-per-directory=.gitignore $old $new 2>&1) || ( case "$merge" in '') echo >&2 "$merge_error" @@ -172,7 +175,8 @@ else git diff-files --name-only | git update-index --remove --stdin && work=`git write-tree` && git read-tree --reset -u $new && - git read-tree -m -u --aggressive $old $new $work || exit + git read-tree -m -u --aggressive --exclude-per-directory=.gitignore $old $new $work || + exit if result=`git write-tree 2>/dev/null` then diff --git a/git-clone.sh b/git-clone.sh index 8c0a93ebd1..f37eb9d105 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -14,7 +14,7 @@ die() { } usage() { - die "Usage: $0 [--template=<template_directory>] [--use-immingled-remote] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [--depth <n>] [-n] <repo> [<dir>]" + die "Usage: $0 [--template=<template_directory>] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [--depth <n>] [-n] <repo> [<dir>]" } get_repo_base() { @@ -48,6 +48,10 @@ Perhaps git-update-server-info needs to be run there?" case "$name" in *^*) continue;; esac + case "$bare,$name" in + yes,* | ,heads/* | ,tags/*) ;; + *) continue ;; + esac if test -n "$use_separate_remote" && branch_name=`expr "z$name" : 'zheads/\(.*\)'` then @@ -134,11 +138,9 @@ while *,--template=*) template="$1" ;; *,-q|*,--quiet) quiet=-q ;; - *,--use-separate-remote) - # default - use_separate_remote=t ;; - *,--use-immingled-remote) - use_separate_remote= ;; + *,--use-separate-remote) ;; + *,--no-separate-remote) + die "clones are always made with separate-remote layout" ;; 1,--reference) usage ;; *,--reference) shift; reference="$1" ;; @@ -177,7 +179,7 @@ repo="$1" test -n "$repo" || die 'you must specify a repository to clone.' -# --bare implies --no-checkout and --use-immingled-remote +# --bare implies --no-checkout and --no-separate-remote if test yes = "$bare" then if test yes = "$origin_override" @@ -336,12 +338,8 @@ cd "$D" || exit if test -z "$bare" && test -f "$GIT_DIR/REMOTE_HEAD" then - # Figure out which remote branch HEAD points at. - case "$use_separate_remote" in - '') remote_top=refs/heads ;; - *) remote_top="refs/remotes/$origin" ;; - esac - + # a non-bare repository is always in separate-remote layout + remote_top="refs/remotes/$origin" head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"` case "$head_sha1" in 'ref: refs/'*) @@ -375,41 +373,28 @@ then ) ) - # Write out remotes/$origin file, and update our "$head_points_at". + # Write out remote.$origin config, and update our "$head_points_at". case "$head_points_at" in ?*) - mkdir -p "$GIT_DIR/remotes" && + # Local default branch git-symbolic-ref HEAD "refs/heads/$head_points_at" && - case "$use_separate_remote" in - t) origin_track="$remote_top/$head_points_at" - git-update-ref HEAD "$head_sha1" ;; - *) origin_track="$remote_top/$origin" - git-update-ref "refs/heads/$origin" "$head_sha1" ;; - esac && - echo >"$GIT_DIR/remotes/$origin" \ - "URL: $repo -Pull: refs/heads/$head_points_at:$origin_track" && - (cd "$GIT_DIR/$remote_top" && find . -type f -print) | - while read dotslref - do - name=`expr "$dotslref" : './\(.*\)'` - if test "z$head_points_at" = "z$name" - then - continue - fi - if test "$use_separate_remote" = '' && - test "z$origin" = "z$name" - then - continue - fi - echo "Pull: refs/heads/${name}:$remote_top/${name}" - done >>"$GIT_DIR/remotes/$origin" && - case "$use_separate_remote" in - t) - rm -f "refs/remotes/$origin/HEAD" - git-symbolic-ref "refs/remotes/$origin/HEAD" \ - "refs/remotes/$origin/$head_points_at" - esac + + # Tracking branch for the primary branch at the remote. + origin_track="$remote_top/$head_points_at" && + git-update-ref HEAD "$head_sha1" && + + # Upstream URL + git-repo-config remote."$origin".url "$repo" && + + # Set up the mappings to track the remote branches. + git-repo-config remote."$origin".fetch \ + "refs/heads/*:$remote_top/*" '^$' && + rm -f "refs/remotes/$origin/HEAD" + git-symbolic-ref "refs/remotes/$origin/HEAD" \ + "refs/remotes/$origin/$head_points_at" && + + git-repo-config branch."$head_points_at".remote "$origin" && + git-repo-config branch."$head_points_at".merge "refs/heads/$head_points_at" esac case "$no_checkout" in diff --git a/git-commit.sh b/git-commit.sh index 81c3a0cb61..6bce41af4d 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -80,6 +80,7 @@ no_edit= log_given= log_message= verify=t +quiet= verbose= signoff= force_author= @@ -241,6 +242,10 @@ $1" signoff=t shift ;; + -q|--q|--qu|--qui|--quie|--quiet) + quiet=t + shift + ;; -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) verbose=t shift @@ -350,19 +355,9 @@ t,) refuse_partial "Cannot do a partial commit during a merge." fi TMP_INDEX="$GIT_DIR/tmp-index$$" - if test -z "$initial_commit" - then - # make sure index is clean at the specified paths, or - # they are additions. - dirty_in_index=`git-diff-index --cached --name-status \ - --diff-filter=DMTU HEAD -- "$@"` - test -z "$dirty_in_index" || - refuse_partial "Different in index and the last commit: -$dirty_in_index" - fi commit_only=`git-ls-files --error-unmatch -- "$@"` || exit - # Build the temporary index and update the real index + # Build a temporary index and update the real index # the same way. if test -z "$initial_commit" then @@ -525,7 +520,7 @@ then current="$(git-rev-parse --verify HEAD)" else if [ -z "$(git-ls-files)" ]; then - echo >&2 Nothing to commit + echo >&2 'nothing to commit (use "git add file1 file2" to include for commit)' exit 1 fi PARENTS="" @@ -625,8 +620,17 @@ then git-rerere fi -if test -x "$GIT_DIR"/hooks/post-commit && test "$ret" = 0 +if test "$ret" = 0 then - "$GIT_DIR"/hooks/post-commit + if test -x "$GIT_DIR"/hooks/post-commit + then + "$GIT_DIR"/hooks/post-commit + fi + if test -z "$quiet" + then + echo "Created${initial_commit:+ initial} commit $commit" + git-diff-tree --shortstat --summary --root --no-commit-id HEAD + fi fi + exit "$ret" diff --git a/git-compat-util.h b/git-compat-util.h index 0272d043d0..5d9eb2615b 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -11,6 +11,13 @@ #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) +#if !defined(__APPLE__) && !defined(__FreeBSD__) +#define _XOPEN_SOURCE 600 /* glibc2 and AIX 5.3L need 500, OpenBSD needs 600 for S_ISLNK() */ +#define _XOPEN_SOURCE_EXTENDED 1 /* AIX 5.3L needs this */ +#endif +#define _GNU_SOURCE +#define _BSD_SOURCE + #include <unistd.h> #include <stdio.h> #include <sys/stat.h> @@ -22,9 +29,27 @@ #include <errno.h> #include <limits.h> #include <sys/param.h> -#include <netinet/in.h> #include <sys/types.h> #include <dirent.h> +#include <sys/time.h> +#include <time.h> +#include <signal.h> +#include <sys/wait.h> +#include <fnmatch.h> +#include <sys/poll.h> +#include <sys/socket.h> +#include <assert.h> +#include <regex.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <pwd.h> +#include <grp.h> + +#ifndef NO_ICONV +#include <iconv.h> +#endif /* On most systems <limits.h> would have given us this, but * not on some systems (e.g. GNU/Hurd). @@ -46,10 +71,12 @@ extern void usage(const char *err) NORETURN; extern void die(const char *err, ...) NORETURN __attribute__((format (printf, 1, 2))); extern int error(const char *err, ...) __attribute__((format (printf, 1, 2))); +extern void warn(const char *err, ...) __attribute__((format (printf, 1, 2))); extern void set_usage_routine(void (*routine)(const char *err) NORETURN); extern void set_die_routine(void (*routine)(const char *err, va_list params) NORETURN); extern void set_error_routine(void (*routine)(const char *err, va_list params)); +extern void set_warn_routine(void (*routine)(const char *warn, va_list params)); #ifdef NO_MMAP @@ -60,10 +87,10 @@ extern void set_error_routine(void (*routine)(const char *err, va_list params)); #define MAP_FAILED ((void*)-1) #endif -#define mmap gitfakemmap -#define munmap gitfakemunmap -extern void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_t offset); -extern int gitfakemunmap(void *start, size_t length); +#define mmap git_mmap +#define munmap git_munmap +extern void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); +extern int git_munmap(void *start, size_t length); #else /* NO_MMAP */ diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index 7bac16e946..4863c91fe3 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -2,9 +2,8 @@ # Known limitations: # - does not propagate permissions -# - tells "ready for commit" even when things could not be completed -# (not sure this is true anymore, more testing is needed) -# - does not handle whitespace in pathnames at all. +# - error handling has not been extensively tested +# use strict; use Getopt::Std; @@ -115,47 +114,40 @@ if ($opt_a) { } close MSG; -my (@afiles, @dfiles, @mfiles, @dirs); -my @files = safe_pipe_capture('git-diff-tree', '-r', $parent, $commit); -#print @files; -$? && die "Error in git-diff-tree"; -foreach my $f (@files) { - chomp $f; - my @fields = split(m!\s+!, $f); - if ($fields[4] eq 'A') { - my $path = $fields[5]; - push @afiles, $path; - # add any needed parent directories - $path = dirname $path; - while (!-d $path and ! grep { $_ eq $path } @dirs) { - unshift @dirs, $path; - $path = dirname $path; - } - } - if ($fields[4] eq 'M') { - push @mfiles, $fields[5]; - } - if ($fields[4] eq 'D') { - push @dfiles, $fields[5]; - } +`git-diff-tree --binary -p $parent $commit >.cvsexportcommit.diff`;# || die "Cannot diff"; + +## apply non-binary changes +my $fuzz = $opt_p ? 0 : 2; + +print "Checking if patch will apply\n"; + +my @stat; +open APPLY, "GIT_DIR= git-apply -C$fuzz --binary --summary --numstat<.cvsexportcommit.diff|" || die "cannot patch"; +@stat=<APPLY>; +close APPLY || die "Cannot patch"; +my (@bfiles,@files,@afiles,@dfiles); +chomp @stat; +foreach (@stat) { + push (@bfiles,$1) if m/^-\t-\t(.*)$/; + push (@files, $1) if m/^-\t-\t(.*)$/; + push (@files, $1) if m/^\d+\t\d+\t(.*)$/; + push (@afiles,$1) if m/^ create mode [0-7]+ (.*)$/; + push (@dfiles,$1) if m/^ delete mode [0-7]+ (.*)$/; } -my (@binfiles, @abfiles, @dbfiles, @bfiles, @mbfiles); -@binfiles = grep m/^Binary files/, safe_pipe_capture('git-diff-tree', '-p', $parent, $commit); -map { chomp } @binfiles; -@abfiles = grep s/^Binary files \/dev\/null and b\/(.*) differ$/$1/, @binfiles; -@dbfiles = grep s/^Binary files a\/(.*) and \/dev\/null differ$/$1/, @binfiles; -@mbfiles = grep s/^Binary files a\/(.*) and b\/(.*) differ$/$1/, @binfiles; -push @bfiles, @abfiles; -push @bfiles, @dbfiles; -push @bfiles, @mbfiles; -push @mfiles, @mbfiles; - -$opt_v && print "The commit affects:\n "; -$opt_v && print join ("\n ", @afiles,@mfiles,@dfiles) . "\n\n"; -undef @files; # don't need it anymore +map { s/^"(.*)"$/$1/g } @bfiles,@files; +map { s/\\([0-7]{3})/sprintf('%c',oct $1)/eg } @bfiles,@files; # check that the files are clean and up to date according to cvs my $dirty; +my @dirs; +foreach my $p (@afiles) { + my $path = dirname $p; + while (!-d $path and ! grep { $_ eq $path } @dirs) { + unshift @dirs, $path; + $path = dirname $path; + } +} + foreach my $d (@dirs) { if (-e $d) { $dirty = 1; @@ -178,7 +170,8 @@ foreach my $f (@afiles) { } } -foreach my $f (@mfiles, @dfiles) { +foreach my $f (@files) { + next if grep { $_ eq $f } @afiles; # TODO:we need to handle removed in cvs my @status = grep(m/^File/, safe_pipe_capture('cvs', '-q', 'status' ,$f)); if (@status > 1) { warn 'Strange! cvs status returned more than one line?'}; @@ -195,78 +188,18 @@ if ($dirty) { } } -### -### NOTE: if you are planning to die() past this point -### you MUST call cleanupcvs(@files) before die() -### - +print "Applying\n"; +`GIT_DIR= git-apply -C$fuzz --binary --summary --numstat --apply <.cvsexportcommit.diff` || die "cannot patch"; -print "Creating new directories\n"; +print "Patch applied successfully. Adding new files and directories to CVS\n"; +my $dirtypatch = 0; foreach my $d (@dirs) { - unless (mkdir $d) { - warn "Could not mkdir $d: $!"; - $dirty = 1; - } - `cvs add $d`; - if ($?) { - $dirty = 1; + if (system('cvs','add',$d)) { + $dirtypatch = 1; warn "Failed to cvs add directory $d -- you may need to do it manually"; } } -print "'Patching' binary files\n"; - -foreach my $f (@bfiles) { - # check that the file in cvs matches the "old" file - # extract the file to $tmpdir and compare with cmp - if (not(grep { $_ eq $f } @afiles)) { - my $tree = safe_pipe_capture('git-rev-parse', "$parent^{tree}"); - chomp $tree; - my $blob = `git-ls-tree $tree "$f" | cut -f 1 | cut -d ' ' -f 3`; - chomp $blob; - `git-cat-file blob $blob > $tmpdir/blob`; - if (system('cmp', '-s', $f, "$tmpdir/blob")) { - warn "Binary file $f in CVS does not match parent.\n"; - if (not $opt_f) { - $dirty = 1; - next; - } - } - } - if (not(grep { $_ eq $f } @dfiles)) { - my $tree = safe_pipe_capture('git-rev-parse', "$commit^{tree}"); - chomp $tree; - my $blob = `git-ls-tree $tree "$f" | cut -f 1 | cut -d ' ' -f 3`; - chomp $blob; - # replace with the new file - `git-cat-file blob $blob > $f`; - } - - # TODO: something smart with file modes - -} -if ($dirty) { - cleanupcvs(@files); - die "Exiting: Binary files in CVS do not match parent"; -} - -## apply non-binary changes -my $fuzz = $opt_p ? 0 : 2; - -print "Patching non-binary files\n"; - -if (scalar(@afiles)+scalar(@dfiles)+scalar(@mfiles) != scalar(@bfiles)) { - print `(git-diff-tree -p $parent -p $commit | patch -p1 -F $fuzz ) 2>&1`; -} - -my $dirtypatch = 0; -if (($? >> 8) == 2) { - cleanupcvs(@files); - die "Exiting: Patch reported serious trouble -- you will have to apply this patch manually"; -} elsif (($? >> 8) == 1) { # some hunks failed to apply - $dirtypatch = 1; -} - foreach my $f (@afiles) { if (grep { $_ eq $f } @bfiles) { system('cvs', 'add','-kb',$f); @@ -274,7 +207,7 @@ foreach my $f (@afiles) { system('cvs', 'add', $f); } if ($?) { - $dirty = 1; + $dirtypatch = 1; warn "Failed to cvs add $f -- you may need to do it manually"; } } @@ -282,35 +215,40 @@ foreach my $f (@afiles) { foreach my $f (@dfiles) { system('cvs', 'rm', '-f', $f); if ($?) { - $dirty = 1; + $dirtypatch = 1; warn "Failed to cvs rm -f $f -- you may need to do it manually"; } } print "Commit to CVS\n"; -print "Patch: $title\n"; -my $commitfiles = join(' ', @afiles, @mfiles, @dfiles); -my $cmd = "cvs commit -F .msg $commitfiles"; +print "Patch title (first comment line): $title\n"; +my @commitfiles = map { unless (m/\s/) { '\''.$_.'\''; } else { $_; }; } (@files); +my $cmd = "cvs commit -F .msg @commitfiles"; if ($dirtypatch) { print "NOTE: One or more hunks failed to apply cleanly.\n"; - print "Resolve the conflicts and then commit using:\n"; + print "You'll need to apply the patch in .cvsexportcommit.diff manually\n"; + print "using a patch program. After applying the patch and resolving the\n"; + print "problems you may commit using:"; print "\n $cmd\n\n"; exit(1); } - if ($opt_c) { print "Autocommit\n $cmd\n"; - print safe_pipe_capture('cvs', 'commit', '-F', '.msg', @afiles, @mfiles, @dfiles); + print safe_pipe_capture('cvs', 'commit', '-F', '.msg', @files); if ($?) { - cleanupcvs(@files); die "Exiting: The commit did not succeed"; } print "Committed successfully to CVS\n"; } else { print "Ready for you to commit, just run:\n\n $cmd\n"; } + +# clean up +unlink(".cvsexportcommit.diff"); +unlink(".msg"); + sub usage { print STDERR <<END; Usage: GIT_DIR=/path/to/.git ${\basename $0} [-h] [-p] [-v] [-c] [-f] [-m msgprefix] [ parent ] commit @@ -318,17 +256,6 @@ END exit(1); } -# ensure cvs is clean before we die -sub cleanupcvs { - my @files = @_; - foreach my $f (@files) { - system('cvs', '-q', 'update', '-C', $f); - if ($?) { - warn "Warning! Failed to cleanup state of $f\n"; - } - } -} - # An alternative to `command` that allows input to be passed as an array # to work around shell problems with weird characters in arguments # if the exec returns non-zero we die @@ -342,3 +269,16 @@ sub safe_pipe_capture { } return wantarray ? @output : join('',@output); } + +sub safe_pipe_capture_blob { + my $output; + if (my $pid = open my $child, '-|') { + local $/; + undef $/; + $output = (<$child>); + close $child or die join(' ',@_).": $! $?"; + } else { + exec(@_) or die "$! $?"; # exec() can fail the executable can't be found + } + return $output; +} diff --git a/git-cvsimport.perl b/git-cvsimport.perl index 4310dea132..c5bf2d19cd 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -29,7 +29,7 @@ use IPC::Open2; $SIG{'PIPE'}="IGNORE"; $ENV{'TZ'}="UTC"; -our($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,$opt_M,$opt_A,$opt_S,$opt_L); +our ($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,$opt_M,$opt_A,$opt_S,$opt_L); my (%conv_author_name, %conv_author_email); sub usage() { @@ -90,15 +90,15 @@ usage if $opt_h; @ARGV <= 1 or usage(); -if($opt_d) { +if ($opt_d) { $ENV{"CVSROOT"} = $opt_d; -} elsif(-f 'CVS/Root') { +} elsif (-f 'CVS/Root') { open my $f, '<', 'CVS/Root' or die 'Failed to open CVS/Root'; $opt_d = <$f>; chomp $opt_d; close $f; $ENV{"CVSROOT"} = $opt_d; -} elsif($ENV{"CVSROOT"}) { +} elsif ($ENV{"CVSROOT"}) { $opt_d = $ENV{"CVSROOT"}; } else { die "CVSROOT needs to be set"; @@ -141,7 +141,7 @@ use File::Temp qw(tempfile); use POSIX qw(strftime dup2); sub new { - my($what,$repo,$subdir) = @_; + my ($what,$repo,$subdir) = @_; $what=ref($what) if ref($what); my $self = {}; @@ -161,38 +161,38 @@ sub new { sub conn { my $self = shift; my $repo = $self->{'fullrep'}; - if($repo =~ s/^:pserver(?:([^:]*)):(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?//) { - my($param,$user,$pass,$serv,$port) = ($1,$2,$3,$4,$5); + if ($repo =~ s/^:pserver(?:([^:]*)):(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?//) { + my ($param,$user,$pass,$serv,$port) = ($1,$2,$3,$4,$5); - my($proxyhost,$proxyport); - if($param && ($param =~ m/proxy=([^;]+)/)) { + my ($proxyhost,$proxyport); + if ($param && ($param =~ m/proxy=([^;]+)/)) { $proxyhost = $1; # Default proxyport, if not specified, is 8080. $proxyport = 8080; - if($ENV{"CVS_PROXY_PORT"}) { + if ($ENV{"CVS_PROXY_PORT"}) { $proxyport = $ENV{"CVS_PROXY_PORT"}; } - if($param =~ m/proxyport=([^;]+)/){ + if ($param =~ m/proxyport=([^;]+)/) { $proxyport = $1; } } $user="anonymous" unless defined $user; my $rr2 = "-"; - unless($port) { + unless ($port) { $rr2 = ":pserver:$user\@$serv:$repo"; $port=2401; } my $rr = ":pserver:$user\@$serv:$port$repo"; - unless($pass) { + unless ($pass) { open(H,$ENV{'HOME'}."/.cvspass") and do { # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z - while(<H>) { + while (<H>) { chomp; s/^\/\d+\s+//; my ($w,$p) = split(/\s/,$_,2); - if($w eq $rr or $w eq $rr2) { + if ($w eq $rr or $w eq $rr2) { $pass = $p; last; } @@ -202,7 +202,7 @@ sub conn { $pass="A" unless $pass; my ($s, $rep); - if($proxyhost) { + if ($proxyhost) { # Use a HTTP Proxy. Only works for HTTP proxies that # don't require user authentication @@ -218,7 +218,7 @@ sub conn { $rep = <$s>; # The answer should look like 'HTTP/1.x 2yy ....' - if(!($rep =~ m#^HTTP/1\.. 2[0-9][0-9]#)) { + if (!($rep =~ m#^HTTP/1\.. 2[0-9][0-9]#)) { die "Proxy connect: $rep\n"; } # Skip up to the empty line of the proxy server output @@ -239,7 +239,7 @@ sub conn { $rep = <$s>; - if($rep ne "I LOVE YOU\n") { + if ($rep ne "I LOVE YOU\n") { $rep="<unknown>" unless $rep; die "AuthReply: $rep\n"; } @@ -271,7 +271,7 @@ sub conn { } } - unless($pid) { + unless ($pid) { $pr->writer(); $pw->reader(); dup2($pw->fileno(),0); @@ -294,7 +294,7 @@ sub conn { $self->{'socketo'}->flush(); chomp(my $rep=$self->readline()); - if($rep !~ s/^Valid-requests\s*//) { + if ($rep !~ s/^Valid-requests\s*//) { $rep="<unknown>" unless $rep; die "Expected Valid-requests from server, but got: $rep\n"; } @@ -306,14 +306,14 @@ sub conn { } sub readline { - my($self) = @_; + my ($self) = @_; return $self->{'socketi'}->getline(); } sub _file { # Request a file with a given revision. # Trial and error says this is a good way to do it. :-/ - my($self,$fn,$rev) = @_; + my ($self,$fn,$rev) = @_; $self->{'socketo'}->write("Argument -N\n") or return undef; $self->{'socketo'}->write("Argument -P\n") or return undef; # -kk: Linus' version doesn't use it - defaults to off @@ -335,12 +335,12 @@ sub _file { sub _line { # Read a line from the server. # ... except that 'line' may be an entire file. ;-) - my($self, $fh) = @_; + my ($self, $fh) = @_; die "Not in lines" unless defined $self->{'lines'}; my $line; my $res=0; - while(defined($line = $self->readline())) { + while (defined($line = $self->readline())) { # M U gnupg-cvs-rep/AUTHORS # Updated gnupg-cvs-rep/ # /daten/src/rsync/gnupg-cvs-rep/AUTHORS @@ -349,7 +349,7 @@ sub _line { # 0 # ok - if($line =~ s/^(?:Created|Updated) //) { + if ($line =~ s/^(?:Created|Updated) //) { $line = $self->readline(); # path $line = $self->readline(); # Entries line my $mode = $self->readline(); chomp $mode; @@ -360,12 +360,12 @@ sub _line { die "Duh: Filesize $cnt" if $cnt !~ /^\d+$/; $line=""; $res = $self->_fetchfile($fh, $cnt); - } elsif($line =~ s/^ //) { + } elsif ($line =~ s/^ //) { print $fh $line; $res += length($line); - } elsif($line =~ /^M\b/) { + } elsif ($line =~ /^M\b/) { # output, do nothing - } elsif($line =~ /^Mbinary\b/) { + } elsif ($line =~ /^Mbinary\b/) { my $cnt; die "EOF from server after 'Mbinary'" unless defined ($cnt = $self->readline()); chomp $cnt; @@ -374,12 +374,12 @@ sub _line { $res += $self->_fetchfile($fh, $cnt); } else { chomp $line; - if($line eq "ok") { + if ($line eq "ok") { # print STDERR "S: ok (".length($res).")\n"; return $res; - } elsif($line =~ s/^E //) { + } elsif ($line =~ s/^E //) { # print STDERR "S: $line\n"; - } elsif($line =~ /^(Remove-entry|Removed) /i) { + } elsif ($line =~ /^(Remove-entry|Removed) /i) { $line = $self->readline(); # filename $line = $self->readline(); # OK chomp $line; @@ -393,7 +393,7 @@ sub _line { return undef; } sub file { - my($self,$fn,$rev) = @_; + my ($self,$fn,$rev) = @_; my $res; my ($fh, $name) = tempfile('gitcvs.XXXXXX', @@ -417,7 +417,7 @@ sub _fetchfile { my ($self, $fh, $cnt) = @_; my $res = 0; my $bufsize = 1024 * 1024; - while($cnt) { + while ($cnt) { if ($bufsize > $cnt) { $bufsize = $cnt; } @@ -438,7 +438,7 @@ my $cvs = CVSconn->new($opt_d, $cvs_tree); sub pdate($) { - my($d) = @_; + my ($d) = @_; m#(\d{2,4})/(\d\d)/(\d\d)\s(\d\d):(\d\d)(?::(\d\d))?# or die "Unparseable date: $d\n"; my $y=$1; $y-=1900 if $y>1900; @@ -446,22 +446,22 @@ sub pdate($) { } sub pmode($) { - my($mode) = @_; + my ($mode) = @_; my $m = 0; my $mm = 0; my $um = 0; for my $x(split(//,$mode)) { - if($x eq ",") { + if ($x eq ",") { $m |= $mm&$um; $mm = 0; $um = 0; - } elsif($x eq "u") { $um |= 0700; - } elsif($x eq "g") { $um |= 0070; - } elsif($x eq "o") { $um |= 0007; - } elsif($x eq "r") { $mm |= 0444; - } elsif($x eq "w") { $mm |= 0222; - } elsif($x eq "x") { $mm |= 0111; - } elsif($x eq "=") { # do nothing + } elsif ($x eq "u") { $um |= 0700; + } elsif ($x eq "g") { $um |= 0070; + } elsif ($x eq "o") { $um |= 0007; + } elsif ($x eq "r") { $mm |= 0444; + } elsif ($x eq "w") { $mm |= 0222; + } elsif ($x eq "x") { $mm |= 0111; + } elsif ($x eq "=") { # do nothing } else { die "Unknown mode: $mode\n"; } } @@ -485,7 +485,7 @@ sub get_headref ($$) { my $git_dir = shift; my $f = "$git_dir/refs/heads/$name"; - if(open(my $fh, $f)) { + if (open(my $fh, $f)) { chomp(my $r = <$fh>); is_sha1($r) or die "Cannot get head id for $name ($r): $!"; return $r; @@ -512,7 +512,7 @@ $orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE}; my %index; # holds filenames of one index per branch -unless(-d $git_dir) { +unless (-d $git_dir) { system("git-init-db"); die "Cannot init the GIT db at $git_tree: $?\n" if $?; system("git-read-tree"); @@ -531,7 +531,7 @@ unless(-d $git_dir) { chomp ($last_branch = <F>); $last_branch = basename($last_branch); close(F); - unless($last_branch) { + unless ($last_branch) { warn "Cannot read the last branch name: $! -- assuming 'master'\n"; $last_branch = "master"; } @@ -542,7 +542,7 @@ unless(-d $git_dir) { my $fmt = '($ref, $author) = (%(refname), %(author));'; open(H, "git-for-each-ref --perl --format='$fmt' refs/heads |") or die "Cannot run git-for-each-ref: $!\n"; - while(defined(my $entry = <H>)) { + while (defined(my $entry = <H>)) { my ($ref, $author); eval($entry) || die "cannot eval refs list: $@"; my ($head) = ($ref =~ m|^refs/heads/(.*)|); @@ -572,7 +572,7 @@ unless ($opt_P) { print "Running cvsps...\n" if $opt_v; my $pid = open(CVSPS,"-|"); die "Cannot fork: $!\n" unless defined $pid; - unless($pid) { + unless ($pid) { my @opt; @opt = split(/,/,$opt_p) if defined $opt_p; unshift @opt, '-z', $opt_z if defined $opt_z; @@ -642,8 +642,8 @@ sub write_tree () { return $tree; } -my($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg); -my(@old,@new,@skipped,%ignorebranch); +my ($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg); +my (@old,@new,@skipped,%ignorebranch); # commits that cvsps cannot place anywhere... $ignorebranch{'#CVSPS_NO_BRANCH'} = 1; @@ -684,7 +684,7 @@ sub commit { foreach my $rx (@mergerx) { next unless $logmsg =~ $rx && $1; my $mparent = $1 eq 'HEAD' ? $opt_o : $1; - if(my $sha1 = get_headref($mparent, $git_dir)) { + if (my $sha1 = get_headref($mparent, $git_dir)) { push @commit_args, '-p', $mparent; print "Merge parent branch: $mparent\n" if $opt_v; } @@ -725,9 +725,9 @@ sub commit { system("git-update-ref refs/heads/$branch $cid") == 0 or die "Cannot write branch $branch for update: $!\n"; - if($tag) { - my($in, $out) = ('',''); - my($xtag) = $tag; + if ($tag) { + my ($in, $out) = ('',''); + my ($xtag) = $tag; $xtag =~ s/\s+\*\*.*$//; # Remove stuff like ** INVALID ** and ** FUNKY ** $xtag =~ tr/_/\./ if ( $opt_u ); $xtag =~ s/[\/]/$opt_s/g; @@ -762,25 +762,25 @@ sub commit { }; my $commitcount = 1; -while(<CVS>) { +while (<CVS>) { chomp; - if($state == 0 and /^-+$/) { + if ($state == 0 and /^-+$/) { $state = 1; - } elsif($state == 0) { + } elsif ($state == 0) { $state = 1; redo; - } elsif(($state==0 or $state==1) and s/^PatchSet\s+//) { + } elsif (($state==0 or $state==1) and s/^PatchSet\s+//) { $patchset = 0+$_; $state=2; - } elsif($state == 2 and s/^Date:\s+//) { + } elsif ($state == 2 and s/^Date:\s+//) { $date = pdate($_); - unless($date) { + unless ($date) { print STDERR "Could not parse date: $_\n"; $state=0; next; } $state=3; - } elsif($state == 3 and s/^Author:\s+//) { + } elsif ($state == 3 and s/^Author:\s+//) { s/\s+$//; if (/^(.*?)\s+<(.*)>/) { ($author_name, $author_email) = ($1, $2); @@ -791,34 +791,34 @@ while(<CVS>) { $author_name = $author_email = $_; } $state = 4; - } elsif($state == 4 and s/^Branch:\s+//) { + } elsif ($state == 4 and s/^Branch:\s+//) { s/\s+$//; s/[\/]/$opt_s/g; $branch = $_; $state = 5; - } elsif($state == 5 and s/^Ancestor branch:\s+//) { + } elsif ($state == 5 and s/^Ancestor branch:\s+//) { s/\s+$//; $ancestor = $_; $ancestor = $opt_o if $ancestor eq "HEAD"; $state = 6; - } elsif($state == 5) { + } elsif ($state == 5) { $ancestor = undef; $state = 6; redo; - } elsif($state == 6 and s/^Tag:\s+//) { + } elsif ($state == 6 and s/^Tag:\s+//) { s/\s+$//; - if($_ eq "(none)") { + if ($_ eq "(none)") { $tag = undef; } else { $tag = $_; } $state = 7; - } elsif($state == 7 and /^Log:/) { + } elsif ($state == 7 and /^Log:/) { $logmsg = ""; $state = 8; - } elsif($state == 8 and /^Members:/) { + } elsif ($state == 8 and /^Members:/) { $branch = $opt_o if $branch eq "HEAD"; - if(defined $branch_date{$branch} and $branch_date{$branch} >= $date) { + if (defined $branch_date{$branch} and $branch_date{$branch} >= $date) { # skip print "skip patchset $patchset: $date before $branch_date{$branch}\n" if $opt_v; $state = 11; @@ -829,17 +829,17 @@ while(<CVS>) { $state = 11; next; } - if($ancestor) { - if($ancestor eq $branch) { + if ($ancestor) { + if ($ancestor eq $branch) { print STDERR "Branch $branch erroneously stems from itself -- changed ancestor to $opt_o\n"; $ancestor = $opt_o; } - if(-f "$git_dir/refs/heads/$branch") { + if (-f "$git_dir/refs/heads/$branch") { print STDERR "Branch $branch already exists!\n"; $state=11; next; } - unless(open(H,"$git_dir/refs/heads/$ancestor")) { + unless (open(H,"$git_dir/refs/heads/$ancestor")) { print STDERR "Branch $ancestor does not exist!\n"; $ignorebranch{$branch} = 1; $state=11; @@ -847,7 +847,7 @@ while(<CVS>) { } chomp(my $id = <H>); close(H); - unless(open(H,"> $git_dir/refs/heads/$branch")) { + unless (open(H,"> $git_dir/refs/heads/$branch")) { print STDERR "Could not create branch $branch: $!\n"; $ignorebranch{$branch} = 1; $state=11; @@ -860,9 +860,9 @@ while(<CVS>) { } $last_branch = $branch if $branch ne $last_branch; $state = 9; - } elsif($state == 8) { + } elsif ($state == 8) { $logmsg .= "$_\n"; - } elsif($state == 9 and /^\s+(.+?):(INITIAL|\d+(?:\.\d+)+)->(\d+(?:\.\d+)+)\s*$/) { + } elsif ($state == 9 and /^\s+(.+?):(INITIAL|\d+(?:\.\d+)+)->(\d+(?:\.\d+)+)\s*$/) { # VERSION:1.96->1.96.2.1 my $init = ($2 eq "INITIAL"); my $fn = $1; @@ -875,7 +875,7 @@ while(<CVS>) { } print "Fetching $fn v $rev\n" if $opt_v; my ($tmpname, $size) = $cvs->file($fn,$rev); - if($size == -1) { + if ($size == -1) { push(@old,$fn); print "Drop $fn\n" if $opt_v; } else { @@ -893,14 +893,14 @@ while(<CVS>) { push(@new,[$mode, $sha, $fn]); # may be resurrected! } unlink($tmpname); - } elsif($state == 9 and /^\s+(.+?):\d+(?:\.\d+)+->(\d+(?:\.\d+)+)\(DEAD\)\s*$/) { + } elsif ($state == 9 and /^\s+(.+?):\d+(?:\.\d+)+->(\d+(?:\.\d+)+)\(DEAD\)\s*$/) { my $fn = $1; $fn =~ s#^/+##; push(@old,$fn); print "Delete $fn\n" if $opt_v; - } elsif($state == 9 and /^\s*$/) { + } elsif ($state == 9 and /^\s*$/) { $state = 10; - } elsif(($state == 9 or $state == 10) and /^-+$/) { + } elsif (($state == 9 or $state == 10) and /^-+$/) { $commitcount++; if ($opt_L && $commitcount > $opt_L) { last; @@ -910,11 +910,11 @@ while(<CVS>) { system("git repack -a -d"); } $state = 1; - } elsif($state == 11 and /^-+$/) { + } elsif ($state == 11 and /^-+$/) { $state = 1; - } elsif(/^-+$/) { # end of unknown-line processing + } elsif (/^-+$/) { # end of unknown-line processing $state = 1; - } elsif($state != 11) { # ignore stuff when skipping + } elsif ($state != 11) { # ignore stuff when skipping print "* UNKNOWN LINE * $_\n"; } } @@ -943,7 +943,7 @@ if (defined $orig_git_index) { } # Now switch back to the branch we were in before all of this happened -if($orig_branch) { +if ($orig_branch) { print "DONE.\n" if $opt_v; if ($opt_i) { exit 0; diff --git a/git-cvsserver.perl b/git-cvsserver.perl index ca519b7e49..df395126b8 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -17,6 +17,7 @@ use strict; use warnings; +use bytes; use Fcntl; use File::Temp qw/tempdir tempfile/; @@ -945,7 +946,7 @@ sub req_update $log->debug("Temporary directory for merge is $dir"); - my $return = system("merge", $file_local, $file_old, $file_new); + my $return = system("git", "merge-file", $file_local, $file_old, $file_new); $return >>= 8; if ( $return == 0 ) diff --git a/git-fetch.sh b/git-fetch.sh index f0645d97c9..5f316053fb 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -2,7 +2,13 @@ # USAGE='<fetch-options> <repository> <refspec>...' +SUBDIRECTORY_OK=Yes . git-sh-setup +TOP=$(git-rev-parse --show-cdup) +if test ! -z "$TOP" +then + cd "$TOP" +fi . git-parse-remote _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40" @@ -96,6 +102,10 @@ then : >"$GIT_DIR/FETCH_HEAD" fi +# Global that is reused later +ls_remote_result=$(git ls-remote $upload_pack "$remote") || + die "Cannot get the repository state from $remote" + append_fetch_head () { head_="$1" remote_="$2" @@ -240,11 +250,8 @@ esac reflist=$(get_remote_refs_for_fetch "$@") if test "$tags" then - taglist=`IFS=" " && - ( - git-ls-remote $upload_pack --tags "$remote" || - echo fail ouch - ) | + taglist=`IFS=' ' && + echo "$ls_remote_result" | while read sha1 name do case "$sha1" in @@ -253,6 +260,8 @@ then esac case "$name" in *^*) continue ;; + refs/tags/*) ;; + *) continue ;; esac if git-check-ref-format "$name" then @@ -314,22 +323,20 @@ fetch_main () { "`git-repo-config --bool http.noEPSV`" = true ]; then noepsv_opt="--disable-epsv" fi - max_depth=5 - depth=0 - head="ref: $remote_name" - while (expr "z$head" : "zref:" && expr $depth \< $max_depth) >/dev/null - do - remote_name_quoted=$(@@PERL@@ -e ' - my $u = $ARGV[0]; - $u =~ s/^ref:\s*//; - $u =~ s{([^-a-zA-Z0-9/.])}{sprintf"%%%02x",ord($1)}eg; - print "$u"; - ' "$head") - head=$(curl -nsfL $curl_extra_args $noepsv_opt "$remote/$remote_name_quoted") - depth=$( expr \( $depth + 1 \) ) - done + + # Find $remote_name from ls-remote output. + head=$( + IFS=' ' + echo "$ls_remote_result" | + while read sha1 name + do + test "z$name" = "z$remote_name" || continue + echo "$sha1" + break + done + ) expr "z$head" : "z$_x40\$" >/dev/null || - die "Failed to fetch $remote_name from $remote" + die "No such ref $remote_name at $remote" echo >&2 "Fetching $remote_name from $remote using $proto" git-http-fetch -v -a "$head" "$remote/" || exit ;; @@ -371,7 +378,7 @@ fetch_main () { esac append_fetch_head "$head" "$remote" \ - "$remote_name" "$remote_nick" "$local_name" "$not_for_merge" + "$remote_name" "$remote_nick" "$local_name" "$not_for_merge" || exit done @@ -425,15 +432,16 @@ fetch_main () { done local_name=$(expr "z$found" : 'z[^:]*:\(.*\)') append_fetch_head "$sha1" "$remote" \ - "$remote_name" "$remote_nick" "$local_name" "$not_for_merge" - done + "$remote_name" "$remote_nick" "$local_name" \ + "$not_for_merge" || exit + done && if [ "$pack_lockfile" ]; then rm -f "$pack_lockfile"; fi ) || exit ;; esac } -fetch_main "$reflist" +fetch_main "$reflist" || exit # automated tag following case "$no_tags$tags" in @@ -442,17 +450,11 @@ case "$no_tags$tags" in *:refs/*) # effective only when we are following remote branch # using local tracking branch. - taglist=$(IFS=" " && - git-ls-remote $upload_pack --tags "$remote" | - sed -n -e 's|^\('"$_x40"'\) \(refs/tags/.*\)^{}$|\1 \2|p' \ - -e 's|^\('"$_x40"'\) \(refs/tags/.*\)$|\1 \2|p' | + taglist=$(IFS=' ' && + echo "$ls_remote_result" | + git-show-ref --exclude-existing=refs/tags/ | while read sha1 name do - git-show-ref --verify --quiet -- "$name" && continue - git-check-ref-format "$name" || { - echo >&2 "warning: tag ${name} ignored" - continue - } git-cat-file -t "$sha1" >/dev/null 2>&1 || continue echo >&2 "Auto-following $name" echo ".${name}:${name}" @@ -463,7 +465,7 @@ case "$no_tags$tags" in ?*) # do not deepen a shallow tree when following tags shallow_depth= - fetch_main "$taglist" ;; + fetch_main "$taglist" || exit ;; esac esac diff --git a/git-ls-remote.sh b/git-ls-remote.sh index 0f88953f29..03b624ef33 100755 --- a/git-ls-remote.sh +++ b/git-ls-remote.sh @@ -94,7 +94,7 @@ while read sha1 path do case "$sha1" in failed) - die "Failed to find remote refs" + exit 1 ;; esac case "$path" in refs/heads/*) diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh index c49e4c65af..7d62d7902c 100755 --- a/git-merge-one-file.sh +++ b/git-merge-one-file.sh @@ -104,7 +104,7 @@ case "${1:-.}${2:-.}${3:-.}" in # Be careful for funny filename such as "-L" in "$4", which # would confuse "merge" greatly. src1=`git-unpack-file $2` - merge "$src1" "$orig" "$src2" + git-merge-file "$src1" "$orig" "$src2" ret=$? # Create the working tree file, using "our tree" version from the diff --git a/git-merge-recursive-old.py b/git-merge-recursive-old.py deleted file mode 100755 index 4039435ce4..0000000000 --- a/git-merge-recursive-old.py +++ /dev/null @@ -1,944 +0,0 @@ -#!/usr/bin/python -# -# Copyright (C) 2005 Fredrik Kuivinen -# - -import sys -sys.path.append('''@@GIT_PYTHON_PATH@@''') - -import math, random, os, re, signal, tempfile, stat, errno, traceback -from heapq import heappush, heappop -from sets import Set - -from gitMergeCommon import * - -outputIndent = 0 -def output(*args): - sys.stdout.write(' '*outputIndent) - printList(args) - -originalIndexFile = os.environ.get('GIT_INDEX_FILE', - os.environ.get('GIT_DIR', '.git') + '/index') -temporaryIndexFile = os.environ.get('GIT_DIR', '.git') + \ - '/merge-recursive-tmp-index' -def setupIndex(temporary): - try: - os.unlink(temporaryIndexFile) - except OSError: - pass - if temporary: - newIndex = temporaryIndexFile - else: - newIndex = originalIndexFile - os.environ['GIT_INDEX_FILE'] = newIndex - -# This is a global variable which is used in a number of places but -# only written to in the 'merge' function. - -# cacheOnly == True => Don't leave any non-stage 0 entries in the cache and -# don't update the working directory. -# False => Leave unmerged entries in the cache and update -# the working directory. - -cacheOnly = False - -# The entry point to the merge code -# --------------------------------- - -def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0, ancestor=None): - '''Merge the commits h1 and h2, return the resulting virtual - commit object and a flag indicating the cleanness of the merge.''' - assert(isinstance(h1, Commit) and isinstance(h2, Commit)) - - global outputIndent - - output('Merging:') - output(h1) - output(h2) - sys.stdout.flush() - - if ancestor: - ca = [ancestor] - else: - assert(isinstance(graph, Graph)) - ca = getCommonAncestors(graph, h1, h2) - output('found', len(ca), 'common ancestor(s):') - for x in ca: - output(x) - sys.stdout.flush() - - mergedCA = ca[0] - for h in ca[1:]: - outputIndent = callDepth+1 - [mergedCA, dummy] = merge(mergedCA, h, - 'Temporary merge branch 1', - 'Temporary merge branch 2', - graph, callDepth+1) - outputIndent = callDepth - assert(isinstance(mergedCA, Commit)) - - global cacheOnly - if callDepth == 0: - setupIndex(False) - cacheOnly = False - else: - setupIndex(True) - runProgram(['git-read-tree', h1.tree()]) - cacheOnly = True - - [shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), mergedCA.tree(), - branch1Name, branch2Name) - - if graph and (clean or cacheOnly): - res = Commit(None, [h1, h2], tree=shaRes) - graph.addNode(res) - else: - res = None - - return [res, clean] - -getFilesRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)$', re.S) -def getFilesAndDirs(tree): - files = Set() - dirs = Set() - out = runProgram(['git-ls-tree', '-r', '-z', '-t', tree]) - for l in out.split('\0'): - m = getFilesRE.match(l) - if m: - if m.group(2) == 'tree': - dirs.add(m.group(4)) - elif m.group(2) == 'blob': - files.add(m.group(4)) - - return [files, dirs] - -# Those two global variables are used in a number of places but only -# written to in 'mergeTrees' and 'uniquePath'. They keep track of -# every file and directory in the two branches that are about to be -# merged. -currentFileSet = None -currentDirectorySet = None - -def mergeTrees(head, merge, common, branch1Name, branch2Name): - '''Merge the trees 'head' and 'merge' with the common ancestor - 'common'. The name of the head branch is 'branch1Name' and the name of - the merge branch is 'branch2Name'. Return a tuple (tree, cleanMerge) - where tree is the resulting tree and cleanMerge is True iff the - merge was clean.''' - - assert(isSha(head) and isSha(merge) and isSha(common)) - - if common == merge: - output('Already uptodate!') - return [head, True] - - if cacheOnly: - updateArg = '-i' - else: - updateArg = '-u' - - [out, code] = runProgram(['git-read-tree', updateArg, '-m', - common, head, merge], returnCode = True) - if code != 0: - die('git-read-tree:', out) - - [tree, code] = runProgram('git-write-tree', returnCode=True) - tree = tree.rstrip() - if code != 0: - global currentFileSet, currentDirectorySet - [currentFileSet, currentDirectorySet] = getFilesAndDirs(head) - [filesM, dirsM] = getFilesAndDirs(merge) - currentFileSet.union_update(filesM) - currentDirectorySet.union_update(dirsM) - - entries = unmergedCacheEntries() - renamesHead = getRenames(head, common, head, merge, entries) - renamesMerge = getRenames(merge, common, head, merge, entries) - - cleanMerge = processRenames(renamesHead, renamesMerge, - branch1Name, branch2Name) - for entry in entries: - if entry.processed: - continue - if not processEntry(entry, branch1Name, branch2Name): - cleanMerge = False - - if cleanMerge or cacheOnly: - tree = runProgram('git-write-tree').rstrip() - else: - tree = None - else: - cleanMerge = True - - return [tree, cleanMerge] - -# Low level file merging, update and removal -# ------------------------------------------ - -def mergeFile(oPath, oSha, oMode, aPath, aSha, aMode, bPath, bSha, bMode, - branch1Name, branch2Name): - - merge = False - clean = True - - if stat.S_IFMT(aMode) != stat.S_IFMT(bMode): - clean = False - if stat.S_ISREG(aMode): - mode = aMode - sha = aSha - else: - mode = bMode - sha = bSha - else: - if aSha != oSha and bSha != oSha: - merge = True - - if aMode == oMode: - mode = bMode - else: - mode = aMode - - if aSha == oSha: - sha = bSha - elif bSha == oSha: - sha = aSha - elif stat.S_ISREG(aMode): - assert(stat.S_ISREG(bMode)) - - orig = runProgram(['git-unpack-file', oSha]).rstrip() - src1 = runProgram(['git-unpack-file', aSha]).rstrip() - src2 = runProgram(['git-unpack-file', bSha]).rstrip() - try: - [out, code] = runProgram(['merge', - '-L', branch1Name + '/' + aPath, - '-L', 'orig/' + oPath, - '-L', branch2Name + '/' + bPath, - src1, orig, src2], returnCode=True) - except ProgramError, e: - print >>sys.stderr, e - die("Failed to execute 'merge'. merge(1) is used as the " - "file-level merge tool. Is 'merge' in your path?") - - sha = runProgram(['git-hash-object', '-t', 'blob', '-w', - src1]).rstrip() - - os.unlink(orig) - os.unlink(src1) - os.unlink(src2) - - clean = (code == 0) - else: - assert(stat.S_ISLNK(aMode) and stat.S_ISLNK(bMode)) - sha = aSha - - if aSha != bSha: - clean = False - - return [sha, mode, clean, merge] - -def updateFile(clean, sha, mode, path): - updateCache = cacheOnly or clean - updateWd = not cacheOnly - - return updateFileExt(sha, mode, path, updateCache, updateWd) - -def updateFileExt(sha, mode, path, updateCache, updateWd): - if cacheOnly: - updateWd = False - - if updateWd: - pathComponents = path.split('/') - for x in xrange(1, len(pathComponents)): - p = '/'.join(pathComponents[0:x]) - - try: - createDir = not stat.S_ISDIR(os.lstat(p).st_mode) - except OSError: - createDir = True - - if createDir: - try: - os.mkdir(p) - except OSError, e: - die("Couldn't create directory", p, e.strerror) - - prog = ['git-cat-file', 'blob', sha] - if stat.S_ISREG(mode): - try: - os.unlink(path) - except OSError: - pass - if mode & 0100: - mode = 0777 - else: - mode = 0666 - fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode) - proc = subprocess.Popen(prog, stdout=fd) - proc.wait() - os.close(fd) - elif stat.S_ISLNK(mode): - linkTarget = runProgram(prog) - os.symlink(linkTarget, path) - else: - assert(False) - - if updateWd and updateCache: - runProgram(['git-update-index', '--add', '--', path]) - elif updateCache: - runProgram(['git-update-index', '--add', '--cacheinfo', - '0%o' % mode, sha, path]) - -def setIndexStages(path, - oSHA1, oMode, - aSHA1, aMode, - bSHA1, bMode, - clear=True): - istring = [] - if clear: - istring.append("0 " + ("0" * 40) + "\t" + path + "\0") - if oMode: - istring.append("%o %s %d\t%s\0" % (oMode, oSHA1, 1, path)) - if aMode: - istring.append("%o %s %d\t%s\0" % (aMode, aSHA1, 2, path)) - if bMode: - istring.append("%o %s %d\t%s\0" % (bMode, bSHA1, 3, path)) - - runProgram(['git-update-index', '-z', '--index-info'], - input="".join(istring)) - -def removeFile(clean, path): - updateCache = cacheOnly or clean - updateWd = not cacheOnly - - if updateCache: - runProgram(['git-update-index', '--force-remove', '--', path]) - - if updateWd: - try: - os.unlink(path) - except OSError, e: - if e.errno != errno.ENOENT and e.errno != errno.EISDIR: - raise - try: - os.removedirs(os.path.dirname(path)) - except OSError: - pass - -def uniquePath(path, branch): - def fileExists(path): - try: - os.lstat(path) - return True - except OSError, e: - if e.errno == errno.ENOENT: - return False - else: - raise - - branch = branch.replace('/', '_') - newPath = path + '~' + branch - suffix = 0 - while newPath in currentFileSet or \ - newPath in currentDirectorySet or \ - fileExists(newPath): - suffix += 1 - newPath = path + '~' + branch + '_' + str(suffix) - currentFileSet.add(newPath) - return newPath - -# Cache entry management -# ---------------------- - -class CacheEntry: - def __init__(self, path): - class Stage: - def __init__(self): - self.sha1 = None - self.mode = None - - # Used for debugging only - def __str__(self): - if self.mode != None: - m = '0%o' % self.mode - else: - m = 'None' - - if self.sha1: - sha1 = self.sha1 - else: - sha1 = 'None' - return 'sha1: ' + sha1 + ' mode: ' + m - - self.stages = [Stage(), Stage(), Stage(), Stage()] - self.path = path - self.processed = False - - def __str__(self): - return 'path: ' + self.path + ' stages: ' + repr([str(x) for x in self.stages]) - -class CacheEntryContainer: - def __init__(self): - self.entries = {} - - def add(self, entry): - self.entries[entry.path] = entry - - def get(self, path): - return self.entries.get(path) - - def __iter__(self): - return self.entries.itervalues() - -unmergedRE = re.compile(r'^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S) -def unmergedCacheEntries(): - '''Create a dictionary mapping file names to CacheEntry - objects. The dictionary contains one entry for every path with a - non-zero stage entry.''' - - lines = runProgram(['git-ls-files', '-z', '--unmerged']).split('\0') - lines.pop() - - res = CacheEntryContainer() - for l in lines: - m = unmergedRE.match(l) - if m: - mode = int(m.group(1), 8) - sha1 = m.group(2) - stage = int(m.group(3)) - path = m.group(4) - - e = res.get(path) - if not e: - e = CacheEntry(path) - res.add(e) - - e.stages[stage].mode = mode - e.stages[stage].sha1 = sha1 - else: - die('Error: Merge program failed: Unexpected output from', - 'git-ls-files:', l) - return res - -lsTreeRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)\n$', re.S) -def getCacheEntry(path, origTree, aTree, bTree): - '''Returns a CacheEntry object which doesn't have to correspond to - a real cache entry in Git's index.''' - - def parse(out): - if out == '': - return [None, None] - else: - m = lsTreeRE.match(out) - if not m: - die('Unexpected output from git-ls-tree:', out) - elif m.group(2) == 'blob': - return [m.group(3), int(m.group(1), 8)] - else: - return [None, None] - - res = CacheEntry(path) - - [oSha, oMode] = parse(runProgram(['git-ls-tree', origTree, '--', path])) - [aSha, aMode] = parse(runProgram(['git-ls-tree', aTree, '--', path])) - [bSha, bMode] = parse(runProgram(['git-ls-tree', bTree, '--', path])) - - res.stages[1].sha1 = oSha - res.stages[1].mode = oMode - res.stages[2].sha1 = aSha - res.stages[2].mode = aMode - res.stages[3].sha1 = bSha - res.stages[3].mode = bMode - - return res - -# Rename detection and handling -# ----------------------------- - -class RenameEntry: - def __init__(self, - src, srcSha, srcMode, srcCacheEntry, - dst, dstSha, dstMode, dstCacheEntry, - score): - self.srcName = src - self.srcSha = srcSha - self.srcMode = srcMode - self.srcCacheEntry = srcCacheEntry - self.dstName = dst - self.dstSha = dstSha - self.dstMode = dstMode - self.dstCacheEntry = dstCacheEntry - self.score = score - - self.processed = False - -class RenameEntryContainer: - def __init__(self): - self.entriesSrc = {} - self.entriesDst = {} - - def add(self, entry): - self.entriesSrc[entry.srcName] = entry - self.entriesDst[entry.dstName] = entry - - def getSrc(self, path): - return self.entriesSrc.get(path) - - def getDst(self, path): - return self.entriesDst.get(path) - - def __iter__(self): - return self.entriesSrc.itervalues() - -parseDiffRenamesRE = re.compile('^:([0-7]+) ([0-7]+) ([0-9a-f]{40}) ([0-9a-f]{40}) R([0-9]*)$') -def getRenames(tree, oTree, aTree, bTree, cacheEntries): - '''Get information of all renames which occured between 'oTree' and - 'tree'. We need the three trees in the merge ('oTree', 'aTree' and - 'bTree') to be able to associate the correct cache entries with - the rename information. 'tree' is always equal to either aTree or bTree.''' - - assert(tree == aTree or tree == bTree) - inp = runProgram(['git-diff-tree', '-M', '--diff-filter=R', '-r', - '-z', oTree, tree]) - - ret = RenameEntryContainer() - try: - recs = inp.split("\0") - recs.pop() # remove last entry (which is '') - it = recs.__iter__() - while True: - rec = it.next() - m = parseDiffRenamesRE.match(rec) - - if not m: - die('Unexpected output from git-diff-tree:', rec) - - srcMode = int(m.group(1), 8) - dstMode = int(m.group(2), 8) - srcSha = m.group(3) - dstSha = m.group(4) - score = m.group(5) - src = it.next() - dst = it.next() - - srcCacheEntry = cacheEntries.get(src) - if not srcCacheEntry: - srcCacheEntry = getCacheEntry(src, oTree, aTree, bTree) - cacheEntries.add(srcCacheEntry) - - dstCacheEntry = cacheEntries.get(dst) - if not dstCacheEntry: - dstCacheEntry = getCacheEntry(dst, oTree, aTree, bTree) - cacheEntries.add(dstCacheEntry) - - ret.add(RenameEntry(src, srcSha, srcMode, srcCacheEntry, - dst, dstSha, dstMode, dstCacheEntry, - score)) - except StopIteration: - pass - return ret - -def fmtRename(src, dst): - srcPath = src.split('/') - dstPath = dst.split('/') - path = [] - endIndex = min(len(srcPath), len(dstPath)) - 1 - for x in range(0, endIndex): - if srcPath[x] == dstPath[x]: - path.append(srcPath[x]) - else: - endIndex = x - break - - if len(path) > 0: - return '/'.join(path) + \ - '/{' + '/'.join(srcPath[endIndex:]) + ' => ' + \ - '/'.join(dstPath[endIndex:]) + '}' - else: - return src + ' => ' + dst - -def processRenames(renamesA, renamesB, branchNameA, branchNameB): - srcNames = Set() - for x in renamesA: - srcNames.add(x.srcName) - for x in renamesB: - srcNames.add(x.srcName) - - cleanMerge = True - for path in srcNames: - if renamesA.getSrc(path): - renames1 = renamesA - renames2 = renamesB - branchName1 = branchNameA - branchName2 = branchNameB - else: - renames1 = renamesB - renames2 = renamesA - branchName1 = branchNameB - branchName2 = branchNameA - - ren1 = renames1.getSrc(path) - ren2 = renames2.getSrc(path) - - ren1.dstCacheEntry.processed = True - ren1.srcCacheEntry.processed = True - - if ren1.processed: - continue - - ren1.processed = True - - if ren2: - # Renamed in 1 and renamed in 2 - assert(ren1.srcName == ren2.srcName) - ren2.dstCacheEntry.processed = True - ren2.processed = True - - if ren1.dstName != ren2.dstName: - output('CONFLICT (rename/rename): Rename', - fmtRename(path, ren1.dstName), 'in branch', branchName1, - 'rename', fmtRename(path, ren2.dstName), 'in', - branchName2) - cleanMerge = False - - if ren1.dstName in currentDirectorySet: - dstName1 = uniquePath(ren1.dstName, branchName1) - output(ren1.dstName, 'is a directory in', branchName2, - 'adding as', dstName1, 'instead.') - removeFile(False, ren1.dstName) - else: - dstName1 = ren1.dstName - - if ren2.dstName in currentDirectorySet: - dstName2 = uniquePath(ren2.dstName, branchName2) - output(ren2.dstName, 'is a directory in', branchName1, - 'adding as', dstName2, 'instead.') - removeFile(False, ren2.dstName) - else: - dstName2 = ren2.dstName - setIndexStages(dstName1, - None, None, - ren1.dstSha, ren1.dstMode, - None, None) - setIndexStages(dstName2, - None, None, - None, None, - ren2.dstSha, ren2.dstMode) - - else: - removeFile(True, ren1.srcName) - - [resSha, resMode, clean, merge] = \ - mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode, - ren1.dstName, ren1.dstSha, ren1.dstMode, - ren2.dstName, ren2.dstSha, ren2.dstMode, - branchName1, branchName2) - - if merge or not clean: - output('Renaming', fmtRename(path, ren1.dstName)) - - if merge: - output('Auto-merging', ren1.dstName) - - if not clean: - output('CONFLICT (content): merge conflict in', - ren1.dstName) - cleanMerge = False - - if not cacheOnly: - setIndexStages(ren1.dstName, - ren1.srcSha, ren1.srcMode, - ren1.dstSha, ren1.dstMode, - ren2.dstSha, ren2.dstMode) - - updateFile(clean, resSha, resMode, ren1.dstName) - else: - removeFile(True, ren1.srcName) - - # Renamed in 1, maybe changed in 2 - if renamesA == renames1: - stage = 3 - else: - stage = 2 - - srcShaOtherBranch = ren1.srcCacheEntry.stages[stage].sha1 - srcModeOtherBranch = ren1.srcCacheEntry.stages[stage].mode - - dstShaOtherBranch = ren1.dstCacheEntry.stages[stage].sha1 - dstModeOtherBranch = ren1.dstCacheEntry.stages[stage].mode - - tryMerge = False - - if ren1.dstName in currentDirectorySet: - newPath = uniquePath(ren1.dstName, branchName1) - output('CONFLICT (rename/directory): Rename', - fmtRename(ren1.srcName, ren1.dstName), 'in', branchName1, - 'directory', ren1.dstName, 'added in', branchName2) - output('Renaming', ren1.srcName, 'to', newPath, 'instead') - cleanMerge = False - removeFile(False, ren1.dstName) - updateFile(False, ren1.dstSha, ren1.dstMode, newPath) - elif srcShaOtherBranch == None: - output('CONFLICT (rename/delete): Rename', - fmtRename(ren1.srcName, ren1.dstName), 'in', - branchName1, 'and deleted in', branchName2) - cleanMerge = False - updateFile(False, ren1.dstSha, ren1.dstMode, ren1.dstName) - elif dstShaOtherBranch: - newPath = uniquePath(ren1.dstName, branchName2) - output('CONFLICT (rename/add): Rename', - fmtRename(ren1.srcName, ren1.dstName), 'in', - branchName1 + '.', ren1.dstName, 'added in', branchName2) - output('Adding as', newPath, 'instead') - updateFile(False, dstShaOtherBranch, dstModeOtherBranch, newPath) - cleanMerge = False - tryMerge = True - elif renames2.getDst(ren1.dstName): - dst2 = renames2.getDst(ren1.dstName) - newPath1 = uniquePath(ren1.dstName, branchName1) - newPath2 = uniquePath(dst2.dstName, branchName2) - output('CONFLICT (rename/rename): Rename', - fmtRename(ren1.srcName, ren1.dstName), 'in', - branchName1+'. Rename', - fmtRename(dst2.srcName, dst2.dstName), 'in', branchName2) - output('Renaming', ren1.srcName, 'to', newPath1, 'and', - dst2.srcName, 'to', newPath2, 'instead') - removeFile(False, ren1.dstName) - updateFile(False, ren1.dstSha, ren1.dstMode, newPath1) - updateFile(False, dst2.dstSha, dst2.dstMode, newPath2) - dst2.processed = True - cleanMerge = False - else: - tryMerge = True - - if tryMerge: - - oName, oSHA1, oMode = ren1.srcName, ren1.srcSha, ren1.srcMode - aName, bName = ren1.dstName, ren1.srcName - aSHA1, bSHA1 = ren1.dstSha, srcShaOtherBranch - aMode, bMode = ren1.dstMode, srcModeOtherBranch - aBranch, bBranch = branchName1, branchName2 - - if renamesA != renames1: - aName, bName = bName, aName - aSHA1, bSHA1 = bSHA1, aSHA1 - aMode, bMode = bMode, aMode - aBranch, bBranch = bBranch, aBranch - - [resSha, resMode, clean, merge] = \ - mergeFile(oName, oSHA1, oMode, - aName, aSHA1, aMode, - bName, bSHA1, bMode, - aBranch, bBranch); - - if merge or not clean: - output('Renaming', fmtRename(ren1.srcName, ren1.dstName)) - - if merge: - output('Auto-merging', ren1.dstName) - - if not clean: - output('CONFLICT (rename/modify): Merge conflict in', - ren1.dstName) - cleanMerge = False - - if not cacheOnly: - setIndexStages(ren1.dstName, - oSHA1, oMode, - aSHA1, aMode, - bSHA1, bMode) - - updateFile(clean, resSha, resMode, ren1.dstName) - - return cleanMerge - -# Per entry merge function -# ------------------------ - -def processEntry(entry, branch1Name, branch2Name): - '''Merge one cache entry.''' - - debug('processing', entry.path, 'clean cache:', cacheOnly) - - cleanMerge = True - - path = entry.path - oSha = entry.stages[1].sha1 - oMode = entry.stages[1].mode - aSha = entry.stages[2].sha1 - aMode = entry.stages[2].mode - bSha = entry.stages[3].sha1 - bMode = entry.stages[3].mode - - assert(oSha == None or isSha(oSha)) - assert(aSha == None or isSha(aSha)) - assert(bSha == None or isSha(bSha)) - - assert(oMode == None or type(oMode) is int) - assert(aMode == None or type(aMode) is int) - assert(bMode == None or type(bMode) is int) - - if (oSha and (not aSha or not bSha)): - # - # Case A: Deleted in one - # - if (not aSha and not bSha) or \ - (aSha == oSha and not bSha) or \ - (not aSha and bSha == oSha): - # Deleted in both or deleted in one and unchanged in the other - if aSha: - output('Removing', path) - removeFile(True, path) - else: - # Deleted in one and changed in the other - cleanMerge = False - if not aSha: - output('CONFLICT (delete/modify):', path, 'deleted in', - branch1Name, 'and modified in', branch2Name + '.', - 'Version', branch2Name, 'of', path, 'left in tree.') - mode = bMode - sha = bSha - else: - output('CONFLICT (modify/delete):', path, 'deleted in', - branch2Name, 'and modified in', branch1Name + '.', - 'Version', branch1Name, 'of', path, 'left in tree.') - mode = aMode - sha = aSha - - updateFile(False, sha, mode, path) - - elif (not oSha and aSha and not bSha) or \ - (not oSha and not aSha and bSha): - # - # Case B: Added in one. - # - if aSha: - addBranch = branch1Name - otherBranch = branch2Name - mode = aMode - sha = aSha - conf = 'file/directory' - else: - addBranch = branch2Name - otherBranch = branch1Name - mode = bMode - sha = bSha - conf = 'directory/file' - - if path in currentDirectorySet: - cleanMerge = False - newPath = uniquePath(path, addBranch) - output('CONFLICT (' + conf + '):', - 'There is a directory with name', path, 'in', - otherBranch + '. Adding', path, 'as', newPath) - - removeFile(False, path) - updateFile(False, sha, mode, newPath) - else: - output('Adding', path) - updateFile(True, sha, mode, path) - - elif not oSha and aSha and bSha: - # - # Case C: Added in both (check for same permissions). - # - if aSha == bSha: - if aMode != bMode: - cleanMerge = False - output('CONFLICT: File', path, - 'added identically in both branches, but permissions', - 'conflict', '0%o' % aMode, '->', '0%o' % bMode) - output('CONFLICT: adding with permission:', '0%o' % aMode) - - updateFile(False, aSha, aMode, path) - else: - # This case is handled by git-read-tree - assert(False) - else: - cleanMerge = False - newPath1 = uniquePath(path, branch1Name) - newPath2 = uniquePath(path, branch2Name) - output('CONFLICT (add/add): File', path, - 'added non-identically in both branches. Adding as', - newPath1, 'and', newPath2, 'instead.') - removeFile(False, path) - updateFile(False, aSha, aMode, newPath1) - updateFile(False, bSha, bMode, newPath2) - - elif oSha and aSha and bSha: - # - # case D: Modified in both, but differently. - # - output('Auto-merging', path) - [sha, mode, clean, dummy] = \ - mergeFile(path, oSha, oMode, - path, aSha, aMode, - path, bSha, bMode, - branch1Name, branch2Name) - if clean: - updateFile(True, sha, mode, path) - else: - cleanMerge = False - output('CONFLICT (content): Merge conflict in', path) - - if cacheOnly: - updateFile(False, sha, mode, path) - else: - updateFileExt(sha, mode, path, updateCache=False, updateWd=True) - else: - die("ERROR: Fatal merge failure, shouldn't happen.") - - return cleanMerge - -def usage(): - die('Usage:', sys.argv[0], ' <base>... -- <head> <remote>..') - -# main entry point as merge strategy module -# The first parameters up to -- are merge bases, and the rest are heads. - -if len(sys.argv) < 4: - usage() - -bases = [] -for nextArg in xrange(1, len(sys.argv)): - if sys.argv[nextArg] == '--': - if len(sys.argv) != nextArg + 3: - die('Not handling anything other than two heads merge.') - try: - h1 = firstBranch = sys.argv[nextArg + 1] - h2 = secondBranch = sys.argv[nextArg + 2] - except IndexError: - usage() - break - else: - bases.append(sys.argv[nextArg]) - -print 'Merging', h1, 'with', h2 - -try: - h1 = runProgram(['git-rev-parse', '--verify', h1 + '^0']).rstrip() - h2 = runProgram(['git-rev-parse', '--verify', h2 + '^0']).rstrip() - - if len(bases) == 1: - base = runProgram(['git-rev-parse', '--verify', - bases[0] + '^0']).rstrip() - ancestor = Commit(base, None) - [dummy, clean] = merge(Commit(h1, None), Commit(h2, None), - firstBranch, secondBranch, None, 0, - ancestor) - else: - graph = buildGraph([h1, h2]) - [dummy, clean] = merge(graph.shaMap[h1], graph.shaMap[h2], - firstBranch, secondBranch, graph) - - print '' -except: - if isinstance(sys.exc_info()[1], SystemExit): - raise - else: - traceback.print_exc(None, sys.stderr) - sys.exit(2) - -if clean: - sys.exit(0) -else: - sys.exit(1) diff --git a/git-merge.sh b/git-merge.sh index cb094388bb..7dd0a11236 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -3,22 +3,20 @@ # Copyright (c) 2005 Junio C Hamano # -USAGE='[-n] [--no-commit] [--squash] [-s <strategy>]... <merge-message> <head> <remote>+' +USAGE='[-n] [--no-commit] [--squash] [-s <strategy>] [--reflog-action=<action>] [-m=<merge-message>] <commit>+' + . git-sh-setup LF=' ' -all_strategies='recur recursive recursive-old octopus resolve stupid ours' +all_strategies='recur recursive octopus resolve stupid ours' default_twohead_strategies='recursive' default_octopus_strategies='octopus' no_trivial_merge_strategies='ours' use_strategies= index_merge=t -if test "@@NO_PYTHON@@"; then - all_strategies='recur recursive resolve octopus stupid ours' -fi dropsave() { rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \ @@ -34,7 +32,7 @@ savestate() { restorestate() { if test -f "$GIT_DIR/MERGE_SAVE" then - git reset --hard $head + git reset --hard $head >/dev/null cpio -iuv <"$GIT_DIR/MERGE_SAVE" git-update-index --refresh >/dev/null fi @@ -93,9 +91,25 @@ finish () { esac } +merge_name () { + remote="$1" + rh=$(git-rev-parse --verify "$remote^0" 2>/dev/null) || return + bh=$(git-show-ref -s --verify "refs/heads/$remote" 2>/dev/null) + if test "$rh" = "$bh" + then + echo "$rh branch '$remote' of ." + elif truname=$(expr "$remote" : '\(.*\)~[1-9][0-9]*$') && + git-show-ref -q --verify "refs/heads/$truname" 2>/dev/null + then + echo "$rh branch '$truname' (early part) of ." + else + echo "$rh commit '$remote'" + fi +} + case "$#" in 0) usage ;; esac -rloga= +rloga= have_message= while case "$#" in 0) break ;; esac do case "$1" in @@ -128,17 +142,74 @@ do --reflog-action=*) rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'` ;; + -m=*|--m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*) + merge_msg=`expr "z$1" : 'z-[^=]*=\(.*\)'` + have_message=t + ;; + -m|--m|--me|--mes|--mess|--messa|--messag|--message) + shift + case "$#" in + 1) usage ;; + esac + merge_msg="$1" + have_message=t + ;; -*) usage ;; *) break ;; esac shift done -merge_msg="$1" -shift -head_arg="$1" -head=$(git-rev-parse --verify "$1"^0) || usage -shift +# This could be traditional "merge <msg> HEAD <commit>..." and the +# way we can tell it is to see if the second token is HEAD, but some +# people might have misused the interface and used a committish that +# is the same as HEAD there instead. Traditional format never would +# have "-m" so it is an additional safety measure to check for it. + +if test -z "$have_message" && + second_token=$(git-rev-parse --verify "$2^0" 2>/dev/null) && + head_commit=$(git-rev-parse --verify "HEAD" 2>/dev/null) && + test "$second_token" = "$head_commit" +then + merge_msg="$1" + shift + head_arg="$1" + shift +elif ! git-rev-parse --verify HEAD >/dev/null 2>&1 +then + # If the merged head is a valid one there is no reason to + # forbid "git merge" into a branch yet to be born. We do + # the same for "git pull". + if test 1 -ne $# + then + echo >&2 "Can merge only exactly one commit into empty head" + exit 1 + fi + + rh=$(git rev-parse --verify "$1^0") || + die "$1 - not something we can merge" + + git-update-ref -m "initial pull" HEAD "$rh" "" && + git-read-tree --reset -u HEAD + exit + +else + # We are invoked directly as the first-class UI. + head_arg=HEAD + + # All the rest are the commits being merged; prepare + # the standard merge summary message to be appended to + # the given message. If remote is invalid we will die + # later in the common codepath so we discard the error + # in this loop. + merge_name=$(for remote + do + merge_name "$remote" + done | git-fmt-merge-msg + ) + merge_msg="${merge_msg:+$merge_msg$LF$LF}$merge_name" +fi +head=$(git-rev-parse --verify "$head_arg"^0) || usage # All the rest are remote heads test "$#" = 0 && usage ;# we need at least one remote head. @@ -147,9 +218,11 @@ test "$rloga" = '' && rloga="merge: $@" remoteheads= for remote do - remotehead=$(git-rev-parse --verify "$remote"^0) || + remotehead=$(git-rev-parse --verify "$remote"^0 2>/dev/null) || die "$remote - not something we can merge" remoteheads="${remoteheads}$remotehead " + eval GITHEAD_$remotehead='"$remote"' + export GITHEAD_$remotehead done set x $remoteheads ; shift @@ -202,7 +275,7 @@ f,*) echo "Updating $(git-rev-parse --short $head)..$(git-rev-parse --short $1)" git-update-index --refresh 2>/dev/null new_head=$(git-rev-parse --verify "$1^0") && - git-read-tree -u -v -m $head "$new_head" && + git-read-tree -v -m -u --exclude-per-directory=.gitignore $head "$new_head" && finish "$new_head" "Fast forward" dropsave exit 0 @@ -337,7 +410,14 @@ fi case "$best_strategy" in '') restorestate - echo >&2 "No merge strategy handled the merge." + case "$use_strategies" in + ?*' '?*) + echo >&2 "No merge strategy handled the merge." + ;; + *) + echo >&2 "Merge with strategy $use_strategies failed." + ;; + esac exit 2 ;; "$wt_strategy") diff --git a/git-parse-remote.sh b/git-parse-remote.sh index c325ef761e..144f170155 100755 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -7,18 +7,7 @@ GIT_DIR=$(git-rev-parse --git-dir 2>/dev/null) || :; get_data_source () { case "$1" in */*) - # Not so fast. This could be the partial URL shorthand... - token=$(expr "z$1" : 'z\([^/]*\)/') - remainder=$(expr "z$1" : 'z[^/]*/\(.*\)') - if test "$(git-repo-config --get "remote.$token.url")" - then - echo config-partial - elif test -f "$GIT_DIR/branches/$token" - then - echo branches-partial - else - echo '' - fi + echo '' ;; *) if test "$(git-repo-config --get "remote.$1.url")" @@ -40,12 +29,7 @@ get_remote_url () { data_source=$(get_data_source "$1") case "$data_source" in '') - echo "$1" ;; - config-partial) - token=$(expr "z$1" : 'z\([^/]*\)/') - remainder=$(expr "z$1" : 'z[^/]*/\(.*\)') - url=$(git-repo-config --get "remote.$token.url") - echo "$url/$remainder" + echo "$1" ;; config) git-repo-config --get "remote.$1.url" @@ -54,14 +38,10 @@ get_remote_url () { sed -ne '/^URL: */{ s///p q - }' "$GIT_DIR/remotes/$1" ;; + }' "$GIT_DIR/remotes/$1" + ;; branches) - sed -e 's/#.*//' "$GIT_DIR/branches/$1" ;; - branches-partial) - token=$(expr "z$1" : 'z\([^/]*\)/') - remainder=$(expr "z$1" : 'z[^/]*/\(.*\)') - url=$(sed -e 's/#.*//' "$GIT_DIR/branches/$token") - echo "$url/$remainder" + sed -e 's/#.*//' "$GIT_DIR/branches/$1" ;; *) die "internal error: get-remote-url $1" ;; @@ -77,7 +57,7 @@ get_default_remote () { get_remote_default_refs_for_push () { data_source=$(get_data_source "$1") case "$data_source" in - '' | config-partial | branches | branches-partial) + '' | branches) ;; # no default push mapping, just send matching refs. config) git-repo-config --get-all "remote.$1.push" ;; @@ -90,6 +70,41 @@ get_remote_default_refs_for_push () { esac } +# Called from canon_refs_list_for_fetch -d "$remote", which +# is called from get_remote_default_refs_for_fetch to grok +# refspecs that are retrieved from the configuration, but not +# from get_remote_refs_for_fetch when it deals with refspecs +# supplied on the command line. $ls_remote_result has the list +# of refs available at remote. +expand_refs_wildcard () { + for ref + do + lref=${ref#'+'} + # a non glob pattern is given back as-is. + expr "z$lref" : 'zrefs/.*/\*:refs/.*/\*$' >/dev/null || { + echo "$ref" + continue + } + + from=`expr "z$lref" : 'z\(refs/.*/\)\*:refs/.*/\*$'` + to=`expr "z$lref" : 'zrefs/.*/\*:\(refs/.*/\)\*$'` + local_force= + test "z$lref" = "z$ref" || local_force='+' + echo "$ls_remote_result" | + sed -e '/\^{}$/d' | + ( + IFS=' ' + while read sha1 name + do + # ignore the ones that do not start with $from + mapped=${name#"$from"} + test "z$name" = "z$mapped" && continue + echo "${local_force}${name}:${to}${mapped}" + done + ) + done +} + # Subroutine to canonicalize remote:local notation. canon_refs_list_for_fetch () { # If called from get_remote_default_refs_for_fetch @@ -97,9 +112,12 @@ canon_refs_list_for_fetch () { # or the first one otherwise; add prefix . to the rest # to prevent the secondary branches to be merged by default. merge_branches= + curr_branch= if test "$1" = "-d" then shift ; remote="$1" ; shift + set x $(expand_refs_wildcard "$@") + shift if test "$remote" = "$(get_default_remote)" then curr_branch=$(git-symbolic-ref HEAD | \ @@ -128,8 +146,12 @@ canon_refs_list_for_fetch () { else for merge_branch in $merge_branches do - [ "$remote" = "$merge_branch" ] && - dot_prefix= && break + if test "$remote" = "$merge_branch" || + test "$local" = "$merge_branch" + then + dot_prefix= + break + fi done fi case "$remote" in @@ -158,7 +180,7 @@ canon_refs_list_for_fetch () { get_remote_default_refs_for_fetch () { data_source=$(get_data_source "$1") case "$data_source" in - '' | config-partial | branches-partial) + '') echo "HEAD:" ;; config) canon_refs_list_for_fetch -d "$1" \ diff --git a/git-pull.sh b/git-pull.sh index e23beb685d..1703091bbb 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -76,6 +76,10 @@ merge_head=$(sed -e '/ not-for-merge /d' \ case "$merge_head" in '') + curr_branch=$(git-symbolic-ref HEAD | \ + sed -e 's|^refs/heads/||') + echo >&2 "Warning: No merge candidate found because value of config option + \"branch.${curr_branch}.merge\" does not match any remote branch fetched." echo >&2 "No changes." exit 0 ;; diff --git a/git-rebase.sh b/git-rebase.sh index 546fa446fc..ece31425d0 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -139,6 +139,10 @@ do --skip) if test -d "$dotest" then + if test -d "$GIT_DIR/rr-cache" + then + git-rerere clear + fi prev_head="`cat $dotest/prev_head`" end="`cat $dotest/end`" msgnum="`cat $dotest/msgnum`" @@ -157,6 +161,10 @@ do exit ;; --abort) + if test -d "$GIT_DIR/rr-cache" + then + git-rerere clear + fi if test -d "$dotest" then rm -r "$dotest" @@ -284,6 +292,7 @@ then fi # Rewind the head to "$onto"; this saves our current head in ORIG_HEAD. +echo "First, rewinding head to replay your work on top of it..." git-reset --hard "$onto" # If the $onto is a proper descendant of the tip of the branch, then @@ -302,15 +311,6 @@ then exit $? fi -if test "@@NO_PYTHON@@" && test "$strategy" = "recursive-old" -then - die 'The recursive-old merge strategy is written in Python, -which this installation of git was not configured with. Please consider -a different merge strategy (e.g. recursive, resolve, or stupid) -or install Python and git with Python support.' - -fi - # start doing a rebase with git-merge # this is rename-aware if the recursive (default) strategy is used diff --git a/git-repack.sh b/git-repack.sh index f150a558ca..375434b1dc 100755 --- a/git-repack.sh +++ b/git-repack.sh @@ -62,11 +62,13 @@ case ",$all_into_one," in esac args="$args $local $quiet $no_reuse_delta$extra" -name=$(git-pack-objects --non-empty --all $args </dev/null "$PACKTMP") || +name=$(git-pack-objects --non-empty --all --reflog $args </dev/null "$PACKTMP") || exit 1 if [ -z "$name" ]; then echo Nothing new to pack. else + chmod a-w "$PACKTMP-$name.pack" + chmod a-w "$PACKTMP-$name.idx" if test "$quiet" != '-q'; then echo "Pack pack-$name created." fi diff --git a/git-request-pull.sh b/git-request-pull.sh index 4319e35c62..4eacc3a059 100755 --- a/git-request-pull.sh +++ b/git-request-pull.sh @@ -30,4 +30,4 @@ echo " $url" echo git log $baserev..$headrev | git-shortlog ; -git diff --stat --summary $baserev..$headrev +git diff -M --stat --summary $baserev..$headrev diff --git a/git-rerere.perl b/git-rerere.perl index d3664ff491..4f692091e7 100755 --- a/git-rerere.perl +++ b/git-rerere.perl @@ -154,7 +154,7 @@ sub find_conflict { sub merge { my ($name, $path) = @_; record_preimage($path, "$rr_dir/$name/thisimage"); - unless (system('merge', map { "$rr_dir/$name/${_}image" } + unless (system('git', 'merge-file', map { "$rr_dir/$name/${_}image" } qw(this pre post))) { my $in; open $in, "<$rr_dir/$name/thisimage" or @@ -169,9 +169,66 @@ sub merge { return 0; } +sub garbage_collect_rerere { + # We should allow specifying these from the command line and + # that is why the caller gives @ARGV to us, but I am lazy. + + my $cutoff_noresolve = 15; # two weeks + my $cutoff_resolve = 60; # two months + my @to_remove; + while (<$rr_dir/*/preimage>) { + my ($dir) = /^(.*)\/preimage$/; + my $cutoff = ((-f "$dir/postimage") + ? $cutoff_resolve + : $cutoff_noresolve); + my $age = -M "$_"; + if ($cutoff <= $age) { + push @to_remove, $dir; + } + } + if (@to_remove) { + rmtree(\@to_remove); + } +} + -d "$rr_dir" || exit(0); read_rr(); + +if (@ARGV) { + my $arg = shift @ARGV; + if ($arg eq 'clear') { + for my $path (keys %merge_rr) { + my $name = $merge_rr{$path}; + if (-d "$rr_dir/$name" && + ! -f "$rr_dir/$name/postimage") { + rmtree(["$rr_dir/$name"]); + } + } + unlink $merge_rr; + } + elsif ($arg eq 'status') { + for my $path (keys %merge_rr) { + print $path, "\n"; + } + } + elsif ($arg eq 'diff') { + for my $path (keys %merge_rr) { + my $name = $merge_rr{$path}; + system('diff', ((@ARGV == 0) ? ('-u') : @ARGV), + '-L', "a/$path", '-L', "b/$path", + "$rr_dir/$name/preimage", $path); + } + } + elsif ($arg eq 'gc') { + garbage_collect_rerere(@ARGV); + } + else { + die "$0 unknown command: $arg\n"; + } + exit 0; +} + my %conflict = map { $_ => 1 } find_conflict(); # MERGE_RR records paths with conflicts immediately after merge diff --git a/git-reset.sh b/git-reset.sh index 3133b5bd25..2379db082f 100755 --- a/git-reset.sh +++ b/git-reset.sh @@ -1,28 +1,60 @@ #!/bin/sh - -USAGE='[--mixed | --soft | --hard] [<commit-ish>]' +# +# Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano +# +USAGE='[--mixed | --soft | --hard] [<commit-ish>] [ [--] <paths>...]' +SUBDIRECTORY_OK=Yes . git-sh-setup -update= -reset_type=--mixed -case "$1" in ---mixed | --soft | --hard) - reset_type="$1" +update= reset_type=--mixed +unset rev + +while case $# in 0) break ;; esac +do + case "$1" in + --mixed | --soft | --hard) + reset_type="$1" + ;; + --) + break + ;; + -*) + usage + ;; + *) + rev=$(git-rev-parse --verify "$1") || exit + shift + break + ;; + esac shift - ;; --*) - usage ;; -esac +done -case $# in -0) rev=HEAD ;; -1) rev=$(git-rev-parse --verify "$1") || exit ;; -*) usage ;; -esac +: ${rev=HEAD} rev=$(git-rev-parse --verify $rev^0) || exit -# We need to remember the set of paths that _could_ be left -# behind before a hard reset, so that we can remove them. +# Skip -- in "git reset HEAD -- foo" and "git reset -- foo". +case "$1" in --) shift ;; esac + +# git reset --mixed tree [--] paths... can be used to +# load chosen paths from the tree into the index without +# affecting the working tree nor HEAD. +if test $# != 0 +then + test "$reset_type" == "--mixed" || + die "Cannot do partial $reset_type reset." + git ls-tree -r --full-name $rev -- "$@" | + git update-index --add --index-info || exit + git update-index --refresh + exit +fi + +TOP=$(git-rev-parse --show-cdup) +if test ! -z "$TOP" +then + cd "$TOP" +fi + if test "$reset_type" = "--hard" then update=-u @@ -54,7 +86,12 @@ update_ref_status=$? case "$reset_type" in --hard ) - ;; # Nothing else to do + test $update_ref_status = 0 && { + echo -n "HEAD is now at " + GIT_PAGER= git log --max-count=1 --pretty=oneline \ + --abbrev-commit HEAD + } + ;; --soft ) ;; # Nothing else to do --mixed ) @@ -63,6 +100,7 @@ case "$reset_type" in ;; esac -rm -f "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/rr-cache/MERGE_RR" "$GIT_DIR/SQUASH_MSG" +rm -f "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/rr-cache/MERGE_RR" \ + "$GIT_DIR/SQUASH_MSG" "$GIT_DIR/MERGE_MSG" exit $update_ref_status diff --git a/git-revert.sh b/git-revert.sh index 6eab3c72df..50cc47b063 100755 --- a/git-revert.sh +++ b/git-revert.sh @@ -155,7 +155,7 @@ Conflicts: uniq } >>"$GIT_DIR/MERGE_MSG" echo >&2 "Automatic $me failed. After resolving the conflicts," - echo >&2 "mark the corrected paths with 'git-update-index <paths>'" + echo >&2 "mark the corrected paths with 'git-add <paths>'" echo >&2 "and commit the result." case "$me" in cherry-pick) diff --git a/git-shortlog.perl b/git-shortlog.perl deleted file mode 100755 index 334fec7477..0000000000 --- a/git-shortlog.perl +++ /dev/null @@ -1,234 +0,0 @@ -#!/usr/bin/perl -w - -use strict; -use Getopt::Std; -use File::Basename qw(basename dirname); - -our ($opt_h, $opt_n, $opt_s); -getopts('hns'); - -$opt_h && usage(); - -sub usage { - print STDERR "Usage: ${\basename $0} [-h] [-n] [-s] < <log_data>\n"; - exit(1); -} - -my (%mailmap); -my (%email); -my (%map); -my $pstate = 1; -my $n_records = 0; -my $n_output = 0; - -sub shortlog_entry($$) { - my ($name, $desc) = @_; - my $key = $name; - - $desc =~ s#/pub/scm/linux/kernel/git/#/.../#g; - $desc =~ s#\[PATCH\] ##g; - - # store description in array, in email->{desc list} map - if (exists $map{$key}) { - # grab ref - my $obj = $map{$key}; - - # add desc to array - push(@$obj, $desc); - } else { - # create new array, containing 1 item - my @arr = ($desc); - - # store ref to array - $map{$key} = \@arr; - } -} - -# sort comparison function -sub by_name($$) { - my ($a, $b) = @_; - - uc($a) cmp uc($b); -} -sub by_nbentries($$) { - my ($a, $b) = @_; - my $a_entries = $map{$a}; - my $b_entries = $map{$b}; - - @$b_entries - @$a_entries || by_name $a, $b; -} - -my $sort_method = $opt_n ? \&by_nbentries : \&by_name; - -sub summary_output { - my ($obj, $num, $key); - - foreach $key (sort $sort_method keys %map) { - $obj = $map{$key}; - $num = @$obj; - printf "%s: %u\n", $key, $num; - $n_output += $num; - } -} - -sub shortlog_output { - my ($obj, $num, $key, $desc); - - foreach $key (sort $sort_method keys %map) { - $obj = $map{$key}; - $num = @$obj; - - # output author - printf "%s (%u):\n", $key, $num; - - # output author's 1-line summaries - foreach $desc (reverse @$obj) { - print " $desc\n"; - $n_output++; - } - - # blank line separating author from next author - print "\n"; - } -} - -sub changelog_input { - my ($author, $desc); - - while (<>) { - # get author and email - if ($pstate == 1) { - my ($email); - - next unless /^[Aa]uthor:?\s*(.*?)\s*<(.*)>/; - - $n_records++; - - $author = $1; - $email = $2; - $desc = undef; - - # cset author fixups - if (exists $mailmap{$email}) { - $author = $mailmap{$email}; - } elsif (exists $mailmap{$author}) { - $author = $mailmap{$author}; - } elsif (!$author) { - $author = $email; - } - $email{$author}{$email}++; - $pstate++; - } - - # skip to blank line - elsif ($pstate == 2) { - next unless /^\s*$/; - $pstate++; - } - - # skip to non-blank line - elsif ($pstate == 3) { - next unless /^\s*?(.*)/; - - # skip lines that are obviously not - # a 1-line cset description - next if /^\s*From: /; - - chomp; - $desc = $1; - - &shortlog_entry($author, $desc); - - $pstate = 1; - } - - else { - die "invalid parse state $pstate"; - } - } -} - -sub read_mailmap { - my ($fh, $mailmap) = @_; - while (<$fh>) { - chomp; - if (/^([^#].*?)\s*<(.*)>/) { - $mailmap->{$2} = $1; - } - } -} - -sub setup_mailmap { - read_mailmap(\*DATA, \%mailmap); - if (-f '.mailmap') { - my $fh = undef; - open $fh, '<', '.mailmap'; - read_mailmap($fh, \%mailmap); - close $fh; - } -} - -sub finalize { - #print "\n$n_records records parsed.\n"; - - if ($n_records != $n_output) { - die "parse error: input records != output records\n"; - } - if (0) { - for my $author (sort keys %email) { - my $e = $email{$author}; - for my $email (sort keys %$e) { - print STDERR "$author <$email>\n"; - } - } - } -} - -&setup_mailmap; -&changelog_input; -$opt_s ? &summary_output : &shortlog_output; -&finalize; -exit(0); - - -__DATA__ -# -# Even with git, we don't always have name translations. -# So have an email->real name table to translate the -# (hopefully few) missing names -# -Adrian Bunk <bunk@stusta.de> -Andreas Herrmann <aherrman@de.ibm.com> -Andrew Morton <akpm@osdl.org> -Andrew Vasquez <andrew.vasquez@qlogic.com> -Christoph Hellwig <hch@lst.de> -Corey Minyard <minyard@acm.org> -David Woodhouse <dwmw2@shinybook.infradead.org> -Domen Puncer <domen@coderock.org> -Douglas Gilbert <dougg@torque.net> -Ed L Cashin <ecashin@coraid.com> -Evgeniy Polyakov <johnpol@2ka.mipt.ru> -Felix Moeller <felix@derklecks.de> -Frank Zago <fzago@systemfabricworks.com> -Greg Kroah-Hartman <gregkh@suse.de> -James Bottomley <jejb@mulgrave.(none)> -James Bottomley <jejb@titanic.il.steeleye.com> -Jeff Garzik <jgarzik@pretzel.yyz.us> -Jens Axboe <axboe@suse.de> -Kay Sievers <kay.sievers@vrfy.org> -Mitesh shah <mshah@teja.com> -Morten Welinder <terra@gnome.org> -Morten Welinder <welinder@anemone.rentec.com> -Morten Welinder <welinder@darter.rentec.com> -Morten Welinder <welinder@troll.com> -Nguyen Anh Quynh <aquynh@gmail.com> -Paolo 'Blaisorblade' Giarrusso <blaisorblade@yahoo.it> -Peter A Jonsson <pj@ludd.ltu.se> -Ralf Wildenhues <Ralf.Wildenhues@gmx.de> -Rudolf Marek <R.Marek@sh.cvut.cz> -Rui Saraiva <rmps@joel.ist.utl.pt> -Sachin P Sant <ssant@in.ibm.com> -Santtu Hyrkk,Av(B <santtu.hyrkko@gmail.com> -Simon Kelley <simon@thekelleys.org.uk> -Tejun Heo <htejun@gmail.com> -Tony Luck <tony.luck@intel.com> diff --git a/git-svn.perl b/git-svn.perl index 47cd3e27fe..c2cdceb1d1 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -21,44 +21,46 @@ $ENV{TZ} = 'UTC'; $ENV{LC_ALL} = 'C'; $| = 1; # unbuffer STDOUT -# If SVN:: library support is added, please make the dependencies -# optional and preserve the capability to use the command-line client. -# use eval { require SVN::... } to make it lazy load -# We don't use any modules not in the standard Perl distribution: +# properties that we do not log: +my %SKIP = ( 'svn:wc:ra_dav:version-url' => 1, + 'svn:special' => 1, + 'svn:executable' => 1, + 'svn:entry:committed-rev' => 1, + 'svn:entry:last-author' => 1, + 'svn:entry:uuid' => 1, + 'svn:entry:committed-date' => 1, +); + +sub fatal (@) { print STDERR @_; exit 1 } +require SVN::Core; # use()-ing this causes segfaults for me... *shrug* +require SVN::Ra; +require SVN::Delta; +if ($SVN::Core::VERSION lt '1.1.0') { + fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)\n"; +} +push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor'; +push @SVN::Git::Fetcher::ISA, 'SVN::Delta::Editor'; +*SVN::Git::Fetcher::process_rm = *process_rm; use Carp qw/croak/; use IO::File qw//; use File::Basename qw/dirname basename/; use File::Path qw/mkpath/; use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev pass_through/; -use File::Spec qw//; -use File::Copy qw/copy/; use POSIX qw/strftime/; use IPC::Open3; use Memoize; +use Git qw/command command_oneline command_noisy + command_output_pipe command_input_pipe command_close_pipe/; memoize('revisions_eq'); memoize('cmt_metadata'); memoize('get_commit_time'); -my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib, $AUTH_BATON, $AUTH_CALLBACKS); - -sub nag_lib { - print STDERR <<EOF; -! Please consider installing the SVN Perl libraries (version 1.1.0 or -! newer). You will generally get better performance and fewer bugs, -! especially if you: -! 1) have a case-insensitive filesystem -! 2) replace symlinks with files (and vice-versa) in commits - -EOF -} - -$_use_lib = 1 unless $ENV{GIT_SVN_NO_LIB}; -libsvn_load(); -nag_lib() unless $_use_lib; +my ($SVN); my $_optimize_commits = 1 unless $ENV{GIT_SVN_NO_OPTIMIZE_COMMITS}; my $sha1 = qr/[a-f\d]{40}/; my $sha1_short = qr/[a-f\d]{4,40}/; +my $_esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/; my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit, $_find_copies_harder, $_l, $_cp_similarity, $_cp_remote, $_repack, $_repack_nr, $_repack_flags, $_q, @@ -67,9 +69,10 @@ my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit, $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit, $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m, $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive, - $_username, $_config_dir, $_no_auth_cache); + $_username, $_config_dir, $_no_auth_cache, $_xfer_delta, + $_pager, $_color); my (@_branch_from, %tree_map, %users, %rusers, %equiv); -my ($_svn_co_url_revs, $_svn_pg_peg_revs); +my ($_svn_can_do_switch); my @repo_path_split_cache; my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext, @@ -104,7 +107,12 @@ my %cmd = ( init => [ \&init, "Initialize a repo for tracking" . " (requires URL argument)", \%init_opts ], - commit => [ \&commit, "Commit git revisions to SVN", + dcommit => [ \&dcommit, 'Commit several diffs to merge with upstream', + { 'merge|m|M' => \$_merge, + 'strategy|s=s' => \$_strategy, + 'dry-run|n' => \$_dry_run, + %cmt_opts, %fc_opts } ], + 'set-tree' => [ \&commit, "Set an SVN repository to a git tree-ish", { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ], 'show-ignore' => [ \&show_ignore, "Show svn:ignore listings", { 'revision|r=i' => \$_revision } ], @@ -121,7 +129,12 @@ my %cmd = ( 'no-graft-copy' => \$_no_graft_copy } ], 'multi-init' => [ \&multi_init, 'Initialize multiple trees (like git-svnimport)', - { %multi_opts, %fc_opts } ], + { %multi_opts, %init_opts, + 'revision|r=i' => \$_revision, + 'username=s' => \$_username, + 'config-dir=s' => \$_config_dir, + 'no-auth-cache' => \$_no_auth_cache, + } ], 'multi-fetch' => [ \&multi_fetch, 'Fetch multiple trees (like git-svnimport)', \%fc_opts ], @@ -134,17 +147,14 @@ my %cmd = ( 'show-commit' => \$_show_commit, 'non-recursive' => \$_non_recursive, 'authors-file|A=s' => \$_authors, + 'color' => \$_color, + 'pager=s' => \$_pager, } ], 'commit-diff' => [ \&commit_diff, 'Commit a diff between two trees', { 'message|m=s' => \$_message, 'file|F=s' => \$_file, 'revision|r=s' => \$_revision, %cmt_opts } ], - dcommit => [ \&dcommit, 'Commit several diffs to merge with upstream', - { 'merge|m|M' => \$_merge, - 'strategy|s=s' => \$_strategy, - 'dry-run|n' => \$_dry_run, - %cmt_opts } ], ); my $cmd; @@ -171,7 +181,6 @@ usage(1) unless defined $cmd; init_vars(); load_authors() if $_authors; load_all_refs() if $_branch_all_refs; -svn_compat_check() unless $_use_lib; migration_check() unless $cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/; $cmd{$cmd}->[0]->(@ARGV); exit 0; @@ -212,28 +221,27 @@ sub version { } sub rebuild { - if (quiet_run(qw/git-rev-parse --verify/,"refs/remotes/$GIT_SVN^0")) { + if (!verify_ref("refs/remotes/$GIT_SVN^0")) { copy_remote_ref(); } $SVN_URL = shift or undef; my $newest_rev = 0; if ($_upgrade) { - sys('git-update-ref',"refs/remotes/$GIT_SVN","$GIT_SVN-HEAD"); + command_noisy('update-ref',"refs/remotes/$GIT_SVN"," + $GIT_SVN-HEAD"); } else { check_upgrade_needed(); } - my $pid = open(my $rev_list,'-|'); - defined $pid or croak $!; - if ($pid == 0) { - exec("git-rev-list","refs/remotes/$GIT_SVN") or croak $!; - } + my ($rev_list, $ctx) = command_output_pipe("rev-list", + "refs/remotes/$GIT_SVN"); my $latest; while (<$rev_list>) { chomp; my $c = $_; croak "Non-SHA1: $c\n" unless $c =~ /^$sha1$/o; - my @commit = grep(/^git-svn-id: /,`git-cat-file commit $c`); + my @commit = grep(/^git-svn-id: /, + command(qw/cat-file commit/, $c)); next if (!@commit); # skip merges my ($url, $rev, $uuid) = extract_metadata($commit[$#commit]); if (!defined $rev || !$uuid) { @@ -259,33 +267,7 @@ sub rebuild { print "r$rev = $c\n"; $newest_rev = $rev if ($rev > $newest_rev); } - close $rev_list or croak $?; - - goto out if $_use_lib; - if (!chdir $SVN_WC) { - svn_cmd_checkout($SVN_URL, $latest, $SVN_WC); - chdir $SVN_WC or croak $!; - } - - $pid = fork; - defined $pid or croak $!; - if ($pid == 0) { - my @svn_up = qw(svn up); - push @svn_up, '--ignore-externals' unless $_no_ignore_ext; - sys(@svn_up,"-r$newest_rev"); - $ENV{GIT_INDEX_FILE} = $GIT_SVN_INDEX; - index_changes(); - exec('git-write-tree') or croak $!; - } - waitpid $pid, 0; - croak $? if $?; -out: - if ($_upgrade) { - print STDERR <<""; -Keeping deprecated refs/head/$GIT_SVN-HEAD for now. Please remove it -when you have upgraded your tools and habits to use refs/remotes/$GIT_SVN - - } + command_close_pipe($rev_list, $ctx); } sub init { @@ -303,10 +285,10 @@ sub init { $SVN_URL = $url; unless (-d $GIT_DIR) { - my @init_db = ('git-init-db'); + my @init_db = ('init-db'); push @init_db, "--template=$_template" if defined $_template; push @init_db, "--shared" if defined $_shared; - sys(@init_db); + command_noisy(@init_db); } setup_git_svn(); } @@ -314,77 +296,17 @@ sub init { sub fetch { check_upgrade_needed(); $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url"); - my $ret = $_use_lib ? fetch_lib(@_) : fetch_cmd(@_); - if ($ret->{commit} && quiet_run(qw(git-rev-parse --verify - refs/heads/master^0))) { - sys(qw(git-update-ref refs/heads/master),$ret->{commit}); + my $ret = fetch_lib(@_); + if ($ret->{commit} && !verify_ref('refs/heads/master^0')) { + command_noisy(qw(update-ref refs/heads/master),$ret->{commit}); } return $ret; } -sub fetch_cmd { - my (@parents) = @_; - my @log_args = -d $SVN_WC ? ($SVN_WC) : ($SVN_URL); - unless ($_revision) { - $_revision = -d $SVN_WC ? 'BASE:HEAD' : '0:HEAD'; - } - push @log_args, "-r$_revision"; - push @log_args, '--stop-on-copy' unless $_no_stop_copy; - - my $svn_log = svn_log_raw(@log_args); - - my $base = next_log_entry($svn_log) or croak "No base revision!\n"; - # don't need last_revision from grab_base_rev() because - # user could've specified a different revision to skip (they - # didn't want to import certain revisions into git for whatever - # reason, so trust $base->{revision} instead. - my (undef, $last_commit) = svn_grab_base_rev(); - unless (-d $SVN_WC) { - svn_cmd_checkout($SVN_URL,$base->{revision},$SVN_WC); - chdir $SVN_WC or croak $!; - read_uuid(); - $last_commit = git_commit($base, @parents); - assert_tree($last_commit); - } else { - chdir $SVN_WC or croak $!; - read_uuid(); - # looks like a user manually cp'd and svn switch'ed - unless ($last_commit) { - sys(qw/svn revert -R ./); - assert_svn_wc_clean($base->{revision}); - $last_commit = git_commit($base, @parents); - assert_tree($last_commit); - } - } - my @svn_up = qw(svn up); - push @svn_up, '--ignore-externals' unless $_no_ignore_ext; - my $last = $base; - while (my $log_msg = next_log_entry($svn_log)) { - if ($last->{revision} >= $log_msg->{revision}) { - croak "Out of order: last >= current: ", - "$last->{revision} >= $log_msg->{revision}\n"; - } - # Revert is needed for cases like: - # https://svn.musicpd.org/Jamming/trunk (r166:167), but - # I can't seem to reproduce something like that on a test... - sys(qw/svn revert -R ./); - assert_svn_wc_clean($last->{revision}); - sys(@svn_up,"-r$log_msg->{revision}"); - $last_commit = git_commit($log_msg, $last_commit, @parents); - $last = $log_msg; - } - close $svn_log->{fh}; - $last->{commit} = $last_commit; - return $last; -} - sub fetch_lib { my (@parents) = @_; $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url"); - my $repo; - ($repo, $SVN_PATH) = repo_path_split($SVN_URL); - $SVN_LOG ||= libsvn_connect($repo); - $SVN ||= libsvn_connect($repo); + $SVN ||= libsvn_connect($SVN_URL); my ($last_rev, $last_commit) = svn_grab_base_rev(); my ($base, $head) = libsvn_parse_revision($last_rev); if ($base > $head) { @@ -399,16 +321,16 @@ sub fetch_lib { read_uuid(); if (defined $last_commit) { unless (-e $GIT_SVN_INDEX) { - sys(qw/git-read-tree/, $last_commit); + command_noisy('read-tree', $last_commit); } - chomp (my $x = `git-write-tree`); - my ($y) = (`git-cat-file commit $last_commit` + my $x = command_oneline('write-tree'); + my ($y) = (command(qw/cat-file commit/, $last_commit) =~ /^tree ($sha1)/m); if ($y ne $x) { unlink $GIT_SVN_INDEX or croak $!; - sys(qw/git-read-tree/, $last_commit); + command_noisy('read-tree', $last_commit); } - chomp ($x = `git-write-tree`); + $x = command_oneline('write-tree'); if ($y ne $x) { print STDERR "trees ($last_commit) $y != $x\n", "Something is seriously wrong...\n"; @@ -426,7 +348,7 @@ sub fetch_lib { # performance sucks with it enabled, so it's much # faster to fetch revision ranges instead of relying # on the limiter. - libsvn_get_log($SVN_LOG, '/'.$SVN_PATH, + libsvn_get_log(libsvn_dup_ra($SVN), [''], $min, $max, 0, 1, 1, sub { my $log_msg; @@ -452,6 +374,7 @@ sub fetch_lib { $min = $max + 1; $max += $inc; $max = $head if ($max > $head); + $SVN = libsvn_connect($SVN_URL); } restore_index($index); return { revision => $last_rev, commit => $last_commit }; @@ -471,45 +394,19 @@ sub commit { } my @revs; foreach my $c (@commits) { - chomp(my @tmp = safe_qx('git-rev-parse',$c)); + my @tmp = command('rev-parse',$c); if (scalar @tmp == 1) { push @revs, $tmp[0]; } elsif (scalar @tmp > 1) { - push @revs, reverse (safe_qx('git-rev-list',@tmp)); + push @revs, reverse(command('rev-list',@tmp)); } else { die "Failed to rev-parse $c\n"; } } - chomp @revs; - $_use_lib ? commit_lib(@revs) : commit_cmd(@revs); + commit_lib(@revs); print "Done committing ",scalar @revs," revisions to SVN\n"; } -sub commit_cmd { - my (@revs) = @_; - - chdir $SVN_WC or croak "Unable to chdir $SVN_WC: $!\n"; - my $info = svn_info('.'); - my $fetched = fetch(); - if ($info->{Revision} != $fetched->{revision}) { - print STDERR "There are new revisions that were fetched ", - "and need to be merged (or acknowledged) ", - "before committing.\n"; - exit 1; - } - $info = svn_info('.'); - read_uuid($info); - my $last = $fetched; - foreach my $c (@revs) { - my $mods = svn_checkout_tree($last, $c); - if (scalar @$mods == 0) { - print "Skipping, no changes detected\n"; - next; - } - $last = svn_commit_tree($last, $c); - } -} - sub commit_lib { my (@revs) = @_; my ($r_last, $cmt_last) = svn_grab_base_rev(); @@ -528,7 +425,6 @@ sub commit_lib { my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$"; my $repo; - ($repo, $SVN_PATH) = repo_path_split($SVN_URL); set_svn_commit_env(); foreach my $c (@revs) { my $log_msg = get_commit_message($c, $commit_msg); @@ -537,13 +433,11 @@ sub commit_lib { # can't track down... (it's probably in the SVN code) defined(my $pid = open my $fh, '-|') or croak $!; if (!$pid) { - $SVN_LOG = libsvn_connect($repo); - $SVN = libsvn_connect($repo); my $ed = SVN::Git::Editor->new( { r => $r_last, - ra => $SVN_LOG, + ra => libsvn_dup_ra($SVN), c => $c, - svn_path => $SVN_PATH + svn_path => $SVN->{svn_path}, }, $SVN->get_commit_editor( $log_msg->{msg}, @@ -575,7 +469,7 @@ sub commit_lib { $no = 1; } } - close $fh or croak $?; + close $fh or exit 1; if (! defined $r_new && ! defined $cmt_new) { unless ($no) { die "Failed to parse revision information\n"; @@ -589,11 +483,12 @@ sub commit_lib { } sub dcommit { + my $head = shift || 'HEAD'; my $gs = "refs/remotes/$GIT_SVN"; - chomp(my @refs = safe_qx(qw/git-rev-list --no-merges/, "$gs..HEAD")); + my @refs = command(qw/rev-list --no-merges/, "$gs..$head"); my $last_rev; foreach my $d (reverse @refs) { - if (quiet_run('git-rev-parse','--verify',"$d~1") != 0) { + if (!verify_ref("$d~1")) { die "Commit $d\n", "has no parent commit, and therefore ", "nothing to diff against.\n", @@ -617,7 +512,7 @@ sub dcommit { } return if $_dry_run; fetch(); - my @diff = safe_qx(qw/git-diff-tree HEAD/, $gs); + my @diff = command('diff-tree', 'HEAD', $gs, '--'); my @finish; if (@diff) { @finish = qw/rebase/; @@ -626,45 +521,18 @@ sub dcommit { print STDERR "W: HEAD and $gs differ, using @finish:\n", @diff; } else { print "No changes between current HEAD and $gs\n", - "Hard resetting to the latest $gs\n"; + "Resetting to the latest $gs\n"; @finish = qw/reset --mixed/; } - sys('git', @finish, $gs); + command_noisy(@finish, $gs); } sub show_ignore { $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url"); - $_use_lib ? show_ignore_lib() : show_ignore_cmd(); -} - -sub show_ignore_cmd { - require File::Find or die $!; - if (defined $_revision) { - die "-r/--revision option doesn't work unless the Perl SVN ", - "libraries are used\n"; - } - chdir $SVN_WC or croak $!; - my %ign; - File::Find::find({wanted=>sub{if(lstat $_ && -d _ && -d "$_/.svn"){ - s#^\./##; - @{$ign{$_}} = svn_propget_base('svn:ignore', $_); - }}, no_chdir=>1},'.'); - - print "\n# /\n"; - foreach (@{$ign{'.'}}) { print '/',$_ if /\S/ } - delete $ign{'.'}; - foreach my $i (sort keys %ign) { - print "\n# ",$i,"\n"; - foreach (@{$ign{$i}}) { print '/',$i,'/',$_ if /\S/ } - } -} - -sub show_ignore_lib { my $repo; - ($repo, $SVN_PATH) = repo_path_split($SVN_URL); - $SVN ||= libsvn_connect($repo); + $SVN ||= libsvn_connect($SVN_URL); my $r = defined $_revision ? $_revision : $SVN->get_latest_revnum; - libsvn_traverse_ignore(\*STDOUT, $SVN_PATH, $r); + libsvn_traverse_ignore(\*STDOUT, $SVN->{svn_path}, $r); } sub graft_branches { @@ -674,7 +542,7 @@ sub graft_branches { if (%$grafts) { # temporarily disable our grafts file to make this idempotent - chomp($gr_sha1 = safe_qx(qw/git-hash-object -w/,$gr_file)); + chomp($gr_sha1 = command(qw/hash-object -w/,$gr_file)); rename $gr_file, "$gr_file~$gr_sha1" or croak $!; } @@ -692,11 +560,7 @@ sub graft_branches { } } unless ($_no_graft_copy) { - if ($_use_lib) { - graft_file_copy_lib($grafts,$l_map,$u); - } else { - graft_file_copy_cmd($grafts,$l_map,$u); - } + graft_file_copy_lib($grafts,$l_map,$u); } } graft_tree_joins($grafts); @@ -728,7 +592,7 @@ sub multi_init { unless (-d $GIT_SVN_DIR) { print "GIT_SVN_ID set to 'trunk' for $_trunk\n" if $ch_id; init($_trunk); - sys('git-repo-config', 'svn.trunk', $_trunk); + command_noisy('repo-config', 'svn.trunk', $_trunk); } complete_url_ls_init($url, $_branches, '--branches/-b', ''); complete_url_ls_init($url, $_tags, '--tags/-t', 'tags/'); @@ -765,16 +629,14 @@ sub show_log { } } - my $pid = open(my $log,'-|'); - defined $pid or croak $!; - if (!$pid) { - exec(git_svn_log_cmd($r_min,$r_max), @args) or croak $!; - } - setup_pager(); + config_pager(); + @args = (git_svn_log_cmd($r_min, $r_max), @args); + my $log = command_output_pipe(@args); + run_pager(); my (@k, $c, $d); while (<$log>) { - if (/^commit ($sha1_short)/o) { + if (/^${_esc_color}commit ($sha1_short)/o) { my $cmt = $1; if ($c && cmt_showable($c) && $c->{r} != $r_last) { $r_last = $c->{r}; @@ -783,25 +645,25 @@ sub show_log { } $d = undef; $c = { c => $cmt }; - } elsif (/^author (.+) (\d+) ([\-\+]?\d+)$/) { + } elsif (/^${_esc_color}author (.+) (\d+) ([\-\+]?\d+)$/) { get_author_info($c, $1, $2, $3); - } elsif (/^(?:tree|parent|committer) /) { + } elsif (/^${_esc_color}(?:tree|parent|committer) /) { # ignore - } elsif (/^:\d{6} \d{6} $sha1_short/o) { + } elsif (/^${_esc_color}:\d{6} \d{6} $sha1_short/o) { push @{$c->{raw}}, $_; - } elsif (/^[ACRMDT]\t/) { - # we could add $SVN_PATH here, but that requires + } elsif (/^${_esc_color}[ACRMDT]\t/) { + # we could add $SVN->{svn_path} here, but that requires # remote access at the moment (repo_path_split)... - s#^([ACRMDT])\t# $1 #; + s#^(${_esc_color})([ACRMDT])\t#$1 $2 #; push @{$c->{changed}}, $_; - } elsif (/^diff /) { + } elsif (/^${_esc_color}diff /) { $d = 1; push @{$c->{diff}}, $_; } elsif ($d) { push @{$c->{diff}}, $_; - } elsif (/^ (git-svn-id:.+)$/) { + } elsif (/^${_esc_color} (git-svn-id:.+)$/) { ($c->{url}, $c->{r}, undef) = extract_metadata($1); - } elsif (s/^ //) { + } elsif (s/^${_esc_color} //) { push @{$c->{l}}, $_; } } @@ -816,7 +678,7 @@ sub show_log { process_commit($_, $r_min, $r_max) foreach reverse @k; } out: - close $log; + eval { command_close_pipe($log) }; print '-' x72,"\n" unless $_incremental || $_oneline; } @@ -826,10 +688,6 @@ sub commit_diff_usage { } sub commit_diff { - if (!$_use_lib) { - print STDERR "commit-diff must be used with SVN libraries\n"; - exit 1; - } my $ta = shift or commit_diff_usage(); my $tb = shift or commit_diff_usage(); if (!eval { $SVN_URL = shift || file_to_s("$GIT_SVN_DIR/info/url") }) { @@ -856,10 +714,7 @@ sub commit_diff { $_message ||= get_commit_message($tb, "$GIT_DIR/.svn-commit.tmp.$$")->{msg}; } - my $repo; - ($repo, $SVN_PATH) = repo_path_split($SVN_URL); - $SVN_LOG ||= libsvn_connect($repo); - $SVN ||= libsvn_connect($repo); + $SVN ||= libsvn_connect($SVN_URL); if ($r eq 'HEAD') { $r = $SVN->get_latest_revnum; } elsif ($r !~ /^\d+$/) { @@ -868,8 +723,9 @@ sub commit_diff { my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : (); my $rev_committed; my $ed = SVN::Git::Editor->new({ r => $r, - ra => $SVN_LOG, c => $tb, - svn_path => $SVN_PATH + ra => libsvn_dup_ra($SVN), + c => $tb, + svn_path => $SVN->{svn_path} }, $SVN->get_commit_editor($_message, sub { @@ -877,13 +733,16 @@ sub commit_diff { print "Committed $_[0]\n"; }, @lock) ); - my $mods = libsvn_checkout_tree($ta, $tb, $ed); - if (@$mods == 0) { - print "No changes\n$ta == $tb\n"; - $ed->abort_edit; - } else { - $ed->close_edit; - } + eval { + my $mods = libsvn_checkout_tree($ta, $tb, $ed); + if (@$mods == 0) { + print "No changes\n$ta == $tb\n"; + $ed->abort_edit; + } else { + $ed->close_edit; + } + }; + fatal "$@\n" if $@; $_message = $_file = undef; return $rev_committed; } @@ -895,7 +754,7 @@ sub cmt_showable { return 1 if defined $c->{r}; if ($c->{l} && $c->{l}->[-1] eq "...\n" && $c->{a_raw} =~ /\@([a-f\d\-]+)>$/) { - my @msg = safe_qx(qw/git-cat-file commit/, $c->{c}); + my @msg = command(qw/cat-file commit/, $c->{c}); shift @msg while ($msg[0] ne "\n"); shift @msg; @{$c->{l}} = grep !/^git-svn-id: /, @msg; @@ -906,12 +765,49 @@ sub cmt_showable { return defined $c->{r}; } +sub log_use_color { + return 1 if $_color; + my ($dc, $dcvar); + $dcvar = 'color.diff'; + $dc = `git-repo-config --get $dcvar`; + if ($dc eq '') { + # nothing at all; fallback to "diff.color" + $dcvar = 'diff.color'; + $dc = `git-repo-config --get $dcvar`; + } + chomp($dc); + if ($dc eq 'auto') { + my $pc; + $pc = `git-repo-config --get color.pager`; + if ($pc eq '') { + # does not have it -- fallback to pager.color + $pc = `git-repo-config --bool --get pager.color`; + } + else { + $pc = `git-repo-config --bool --get color.pager`; + if ($?) { + $pc = 'false'; + } + } + chomp($pc); + if (-t *STDOUT || (defined $_pager && $pc eq 'true')) { + return ($ENV{TERM} && $ENV{TERM} ne 'dumb'); + } + return 0; + } + return 0 if $dc eq 'never'; + return 1 if $dc eq 'always'; + chomp($dc = `git-repo-config --bool --get $dcvar`); + return ($dc eq 'true'); +} + sub git_svn_log_cmd { my ($r_min, $r_max) = @_; - my @cmd = (qw/git-log --abbrev-commit --pretty=raw + my @cmd = (qw/log --abbrev-commit --pretty=raw --default/, "refs/remotes/$GIT_SVN"); push @cmd, '-r' unless $_non_recursive; push @cmd, qw/--raw --name-status/ if $_verbose; + push @cmd, '--color' if log_use_color(); return @cmd unless defined $r_max; if ($r_max == $r_min) { push @cmd, '--max-count=1'; @@ -992,8 +888,7 @@ sub complete_url_ls_init { } $var = $url . $var; } - chomp(my @ls = $_use_lib ? libsvn_ls_fullurl($var) - : safe_qx(qw/svn ls --non-interactive/, $var)); + my @ls = libsvn_ls_fullurl($var); my $old = $GIT_SVN; defined(my $pid = fork) or croak $!; if (!$pid) { @@ -1017,7 +912,7 @@ sub complete_url_ls_init { waitpid $pid, 0; croak $? if $?; my ($n) = ($switch =~ /^--(\w+)/); - sys('git-repo-config', "svn.$n", $var); + command_noisy('repo-config', "svn.$n", $var); } sub common_prefix { @@ -1049,11 +944,8 @@ sub graft_tree_joins { git_svn_each(sub { my $i = shift; - defined(my $pid = open my $fh, '-|') or croak $!; - if (!$pid) { - exec qw/git-rev-list --pretty=raw/, - "refs/remotes/$i" or croak $!; - } + my @args = (qw/rev-list --pretty=raw/, "refs/remotes/$i"); + my ($fh, $ctx) = command_output_pipe(@args); while (<$fh>) { next unless /^commit ($sha1)$/o; my $c = $1; @@ -1076,9 +968,7 @@ sub graft_tree_joins { foreach my $p (@{$tree_map{$t}}) { next if $p eq $c; - my $mb = eval { - safe_qx('git-merge-base', $c, $p) - }; + my $mb = eval { command('merge-base', $c, $p) }; next unless ($@ || $?); if (defined $r_a) { # see if SVN says it's a relative @@ -1107,48 +997,16 @@ sub graft_tree_joins { # what should we do when $ct == $s ? } } - close $fh or croak $?; + command_close_pipe($fh, $ctx); }); } -# this isn't funky-filename safe, but good enough for now... -sub graft_file_copy_cmd { - my ($grafts, $l_map, $u) = @_; - my $paths = $l_map->{$u}; - my $pfx = common_prefix([keys %$paths]); - $SVN_URL ||= $u.$pfx; - my $pid = open my $fh, '-|'; - defined $pid or croak $!; - unless ($pid) { - my @exec = qw/svn log -v/; - push @exec, "-r$_revision" if defined $_revision; - exec @exec, $u.$pfx or croak $!; - } - my ($r, $mp) = (undef, undef); - while (<$fh>) { - chomp; - if (/^\-{72}$/) { - $mp = $r = undef; - } elsif (/^r(\d+) \| /) { - $r = $1 unless defined $r; - } elsif (/^Changed paths:/) { - $mp = 1; - } elsif ($mp && m#^ [AR] /(\S.*?) \(from /(\S+?):(\d+)\)$#) { - my ($p1, $p0, $r0) = ($1, $2, $3); - my $c = find_graft_path_commit($paths, $p1, $r); - next unless $c; - find_graft_path_parents($grafts, $paths, $c, $p0, $r0); - } - } -} - sub graft_file_copy_lib { my ($grafts, $l_map, $u) = @_; my $tree_paths = $l_map->{$u}; my $pfx = common_prefix([keys %$tree_paths]); my ($repo, $path) = repo_path_split($u.$pfx); - $SVN_LOG ||= libsvn_connect($repo); - $SVN ||= libsvn_connect($repo); + $SVN = libsvn_connect($repo); my ($base, $head) = libsvn_parse_revision(); my $inc = 1000; @@ -1157,7 +1015,8 @@ sub graft_file_copy_lib { $SVN::Error::handler = \&libsvn_skip_unknown_revs; while (1) { my $pool = SVN::Pool->new; - libsvn_get_log($SVN_LOG, "/$path", $min, $max, 0, 1, 1, + libsvn_get_log(libsvn_dup_ra($SVN), [$path], + $min, $max, 0, 2, 1, sub { libsvn_graft_file_copies($grafts, $tree_paths, $path, @_); @@ -1232,28 +1091,14 @@ sub graft_merge_msg { sub read_uuid { return if $SVN_UUID; - if ($_use_lib) { - my $pool = SVN::Pool->new; - $SVN_UUID = $SVN->get_uuid($pool); - $pool->clear; - } else { - my $info = shift || svn_info('.'); - $SVN_UUID = $info->{'Repository UUID'} or - croak "Repository UUID unreadable\n"; - } + my $pool = SVN::Pool->new; + $SVN_UUID = $SVN->get_uuid($pool); + $pool->clear; } -sub quiet_run { - my $pid = fork; - defined $pid or croak $!; - if (!$pid) { - open my $null, '>', '/dev/null' or croak $!; - open STDERR, '>&', $null or croak $!; - open STDOUT, '>&', $null or croak $!; - exec @_ or croak $!; - } - waitpid $pid, 0; - return $?; +sub verify_ref { + my ($ref) = @_; + eval { command_oneline([ 'rev-parse', $ref ], { STDERR => 0 }) }; } sub repo_path_split { @@ -1267,25 +1112,8 @@ sub repo_path_split { return ($u, $full_url); } } - - if ($_use_lib) { - my $tmp = libsvn_connect($full_url); - my $url = $tmp->get_repos_root; - $full_url =~ s#^\Q$url\E/*##; - push @repo_path_split_cache, qr/^(\Q$url\E)/; - return ($url, $full_url); - } else { - my ($url, $path) = ($full_url =~ m!^([a-z\+]+://[^/]*)(.*)$!i); - $path =~ s#^/+##; - my @paths = split(m#/+#, $path); - while (quiet_run(qw/svn ls --non-interactive/, $url)) { - my $n = shift @paths || last; - $url .= "/$n"; - } - push @repo_path_split_cache, qr/^(\Q$url\E)/; - $path = join('/',@paths); - return ($url, $path); - } + my $tmp = libsvn_connect($full_url); + return ($tmp->{repos_root}, $tmp->{svn_path}); } sub setup_git_svn { @@ -1301,41 +1129,17 @@ sub setup_git_svn { } -sub assert_svn_wc_clean { - return if $_use_lib; - my ($svn_rev) = @_; - croak "$svn_rev is not an integer!\n" unless ($svn_rev =~ /^\d+$/); - my $lcr = svn_info('.')->{'Last Changed Rev'}; - if ($svn_rev != $lcr) { - print STDERR "Checking for copy-tree ... "; - my @diff = grep(/^Index: /,(safe_qx(qw(svn diff), - "-r$lcr:$svn_rev"))); - if (@diff) { - croak "Nope! Expected r$svn_rev, got r$lcr\n"; - } else { - print STDERR "OK!\n"; - } - } - my @status = grep(!/^Performing status on external/,(`svn status`)); - @status = grep(!/^\s*$/,@status); - @status = grep(!/^X/,@status) if $_no_ignore_ext; - if (scalar @status) { - print STDERR "Tree ($SVN_WC) is not clean:\n"; - print STDERR $_ foreach @status; - croak; - } -} - sub get_tree_from_treeish { my ($treeish) = @_; croak "Not a sha1: $treeish\n" unless $treeish =~ /^$sha1$/o; - chomp(my $type = `git-cat-file -t $treeish`); + my $type = command_oneline(qw/cat-file -t/, $treeish); my $expected; while ($type eq 'tag') { - chomp(($treeish, $type) = `git-cat-file tag $treeish`); + ($treeish, $type) = command(qw/cat-file tag/, $treeish); } if ($type eq 'commit') { - $expected = (grep /^tree /,`git-cat-file commit $treeish`)[0]; + $expected = (grep /^tree /, command(qw/cat-file commit/, + $treeish))[0]; ($expected) = ($expected =~ /^tree ($sha1)$/); die "Unable to get tree from $treeish\n" unless $expected; } elsif ($type eq 'tree') { @@ -1346,27 +1150,19 @@ sub get_tree_from_treeish { return $expected; } -sub assert_tree { - return if $_use_lib; - my ($treeish) = @_; - my $expected = get_tree_from_treeish($treeish); - - my $tmpindex = $GIT_SVN_INDEX.'.assert-tmp'; - if (-e $tmpindex) { - unlink $tmpindex or croak $!; - } - my $old_index = set_index($tmpindex); - index_changes(1); - chomp(my $tree = `git-write-tree`); - restore_index($old_index); - if ($tree ne $expected) { - croak "Tree mismatch, Got: $tree, Expected: $expected\n"; +sub get_diff { + my ($from, $treeish) = @_; + print "diff-tree $from $treeish\n"; + my @diff_tree = qw(diff-tree -z -r); + if ($_cp_similarity) { + push @diff_tree, "-C$_cp_similarity"; + } else { + push @diff_tree, '-C'; } - unlink $tmpindex; -} - -sub parse_diff_tree { - my $diff_fh = shift; + push @diff_tree, '--find-copies-harder' if $_find_copies_harder; + push @diff_tree, "-l$_l" if defined $_l; + push @diff_tree, $from, $treeish; + my ($diff_fh, $ctx) = command_output_pipe(@diff_tree); local $/ = "\0"; my $state = 'meta'; my @mods; @@ -1402,170 +1198,10 @@ sub parse_diff_tree { croak "Error parsing $_\n"; } } - close $diff_fh or croak $?; - + command_close_pipe($diff_fh, $ctx); return \@mods; } -sub svn_check_prop_executable { - my $m = shift; - return if -l $m->{file_b}; - if ($m->{mode_b} =~ /755$/) { - chmod((0755 &~ umask),$m->{file_b}) or croak $!; - if ($m->{mode_a} !~ /755$/) { - sys(qw(svn propset svn:executable 1), $m->{file_b}); - } - -x $m->{file_b} or croak "$m->{file_b} is not executable!\n"; - } elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) { - sys(qw(svn propdel svn:executable), $m->{file_b}); - chmod((0644 &~ umask),$m->{file_b}) or croak $!; - -x $m->{file_b} and croak "$m->{file_b} is executable!\n"; - } -} - -sub svn_ensure_parent_path { - my $dir_b = dirname(shift); - svn_ensure_parent_path($dir_b) if ($dir_b ne File::Spec->curdir); - mkpath([$dir_b]) unless (-d $dir_b); - sys(qw(svn add -N), $dir_b) unless (-d "$dir_b/.svn"); -} - -sub precommit_check { - my $mods = shift; - my (%rm_file, %rmdir_check, %added_check); - - my %o = ( D => 0, R => 1, C => 2, A => 3, M => 3, T => 3 ); - foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) { - if ($m->{chg} eq 'R') { - if (-d $m->{file_b}) { - err_dir_to_file("$m->{file_a} => $m->{file_b}"); - } - # dir/$file => dir/file/$file - my $dirname = dirname($m->{file_b}); - while ($dirname ne File::Spec->curdir) { - if ($dirname ne $m->{file_a}) { - $dirname = dirname($dirname); - next; - } - err_file_to_dir("$m->{file_a} => $m->{file_b}"); - } - # baz/zzz => baz (baz is a file) - $dirname = dirname($m->{file_a}); - while ($dirname ne File::Spec->curdir) { - if ($dirname ne $m->{file_b}) { - $dirname = dirname($dirname); - next; - } - err_dir_to_file("$m->{file_a} => $m->{file_b}"); - } - } - if ($m->{chg} =~ /^(D|R)$/) { - my $t = $1 eq 'D' ? 'file_b' : 'file_a'; - $rm_file{ $m->{$t} } = 1; - my $dirname = dirname( $m->{$t} ); - my $basename = basename( $m->{$t} ); - $rmdir_check{$dirname}->{$basename} = 1; - } elsif ($m->{chg} =~ /^(?:A|C)$/) { - if (-d $m->{file_b}) { - err_dir_to_file($m->{file_b}); - } - my $dirname = dirname( $m->{file_b} ); - my $basename = basename( $m->{file_b} ); - $added_check{$dirname}->{$basename} = 1; - while ($dirname ne File::Spec->curdir) { - if ($rm_file{$dirname}) { - err_file_to_dir($m->{file_b}); - } - $dirname = dirname $dirname; - } - } - } - return (\%rmdir_check, \%added_check); - - sub err_dir_to_file { - my $file = shift; - print STDERR "Node change from directory to file ", - "is not supported by Subversion: ",$file,"\n"; - exit 1; - } - sub err_file_to_dir { - my $file = shift; - print STDERR "Node change from file to directory ", - "is not supported by Subversion: ",$file,"\n"; - exit 1; - } -} - - -sub get_diff { - my ($from, $treeish) = @_; - assert_tree($from); - print "diff-tree $from $treeish\n"; - my $pid = open my $diff_fh, '-|'; - defined $pid or croak $!; - if ($pid == 0) { - my @diff_tree = qw(git-diff-tree -z -r); - if ($_cp_similarity) { - push @diff_tree, "-C$_cp_similarity"; - } else { - push @diff_tree, '-C'; - } - push @diff_tree, '--find-copies-harder' if $_find_copies_harder; - push @diff_tree, "-l$_l" if defined $_l; - exec(@diff_tree, $from, $treeish) or croak $!; - } - return parse_diff_tree($diff_fh); -} - -sub svn_checkout_tree { - my ($from, $treeish) = @_; - my $mods = get_diff($from->{commit}, $treeish); - return $mods unless (scalar @$mods); - my ($rm, $add) = precommit_check($mods); - - my %o = ( D => 1, R => 0, C => -1, A => 3, M => 3, T => 3 ); - foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) { - if ($m->{chg} eq 'C') { - svn_ensure_parent_path( $m->{file_b} ); - sys(qw(svn cp), $m->{file_a}, $m->{file_b}); - apply_mod_line_blob($m); - svn_check_prop_executable($m); - } elsif ($m->{chg} eq 'D') { - sys(qw(svn rm --force), $m->{file_b}); - } elsif ($m->{chg} eq 'R') { - svn_ensure_parent_path( $m->{file_b} ); - sys(qw(svn mv --force), $m->{file_a}, $m->{file_b}); - apply_mod_line_blob($m); - svn_check_prop_executable($m); - } elsif ($m->{chg} eq 'M') { - apply_mod_line_blob($m); - svn_check_prop_executable($m); - } elsif ($m->{chg} eq 'T') { - svn_check_prop_executable($m); - apply_mod_line_blob($m); - if ($m->{mode_a} =~ /^120/ && $m->{mode_b} !~ /^120/) { - sys(qw(svn propdel svn:special), $m->{file_b}); - } else { - sys(qw(svn propset svn:special *),$m->{file_b}); - } - } elsif ($m->{chg} eq 'A') { - svn_ensure_parent_path( $m->{file_b} ); - apply_mod_line_blob($m); - sys(qw(svn add), $m->{file_b}); - svn_check_prop_executable($m); - } else { - croak "Invalid chg: $m->{chg}\n"; - } - } - - assert_tree($treeish); - if ($_rmdir) { # remove empty directories - handle_rmdir($rm, $add); - } - assert_tree($treeish); - return $mods; -} - sub libsvn_checkout_tree { my ($from, $treeish, $ed) = @_; my $mods = get_diff($from, $treeish); @@ -1583,57 +1219,15 @@ sub libsvn_checkout_tree { return $mods; } -# svn ls doesn't work with respect to the current working tree, but what's -# in the repository. There's not even an option for it... *sigh* -# (added files don't show up and removed files remain in the ls listing) -sub svn_ls_current { - my ($dir, $rm, $add) = @_; - chomp(my @ls = safe_qx('svn','ls',$dir)); - my @ret = (); - foreach (@ls) { - s#/$##; # trailing slashes are evil - push @ret, $_ unless $rm->{$dir}->{$_}; - } - if (exists $add->{$dir}) { - push @ret, keys %{$add->{$dir}}; - } - return \@ret; -} - -sub handle_rmdir { - my ($rm, $add) = @_; - - foreach my $dir (sort {length $b <=> length $a} keys %$rm) { - my $ls = svn_ls_current($dir, $rm, $add); - next if (scalar @$ls); - sys(qw(svn rm --force),$dir); - - my $dn = dirname $dir; - $rm->{ $dn }->{ basename $dir } = 1; - $ls = svn_ls_current($dn, $rm, $add); - while (scalar @$ls == 0 && $dn ne File::Spec->curdir) { - sys(qw(svn rm --force),$dn); - $dir = basename $dn; - $dn = dirname $dn; - $rm->{ $dn }->{ $dir } = 1; - $ls = svn_ls_current($dn, $rm, $add); - } - } -} - sub get_commit_message { my ($commit, $commit_msg) = (@_); my %log_msg = ( msg => '' ); open my $msg, '>', $commit_msg or croak $!; - chomp(my $type = `git-cat-file -t $commit`); + my $type = command_oneline(qw/cat-file -t/, $commit); if ($type eq 'commit' || $type eq 'tag') { - my $pid = open my $msg_fh, '-|'; - defined $pid or croak $!; - - if ($pid == 0) { - exec('git-cat-file', $type, $commit) or croak $!; - } + my ($msg_fh, $ctx) = command_output_pipe('cat-file', + $type, $commit); my $in_msg = 0; while (<$msg_fh>) { if (!$in_msg) { @@ -1645,7 +1239,7 @@ sub get_commit_message { print $msg $_ or croak $!; } } - close $msg_fh or croak $?; + command_close_pipe($msg_fh, $ctx); } close $msg or croak $!; @@ -1670,64 +1264,9 @@ sub set_svn_commit_env { } } -sub svn_commit_tree { - my ($last, $commit) = @_; - my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$"; - my $log_msg = get_commit_message($commit, $commit_msg); - my ($oneline) = ($log_msg->{msg} =~ /([^\n\r]+)/); - print "Committing $commit: $oneline\n"; - - set_svn_commit_env(); - my @ci_output = safe_qx(qw(svn commit -F),$commit_msg); - $ENV{LC_ALL} = 'C'; - unlink $commit_msg; - my ($committed) = ($ci_output[$#ci_output] =~ /(\d+)/); - if (!defined $committed) { - my $out = join("\n",@ci_output); - print STDERR "W: Trouble parsing \`svn commit' output:\n\n", - $out, "\n\nAssuming English locale..."; - ($committed) = ($out =~ /^Committed revision \d+\./sm); - defined $committed or die " FAILED!\n", - "Commit output failed to parse committed revision!\n", - print STDERR " OK\n"; - } - - my @svn_up = qw(svn up); - push @svn_up, '--ignore-externals' unless $_no_ignore_ext; - if ($_optimize_commits && ($committed == ($last->{revision} + 1))) { - push @svn_up, "-r$committed"; - sys(@svn_up); - my $info = svn_info('.'); - my $date = $info->{'Last Changed Date'} or die "Missing date\n"; - if ($info->{'Last Changed Rev'} != $committed) { - croak "$info->{'Last Changed Rev'} != $committed\n" - } - my ($Y,$m,$d,$H,$M,$S,$tz) = ($date =~ - /(\d{4})\-(\d\d)\-(\d\d)\s - (\d\d)\:(\d\d)\:(\d\d)\s([\-\+]\d+)/x) - or croak "Failed to parse date: $date\n"; - $log_msg->{date} = "$tz $Y-$m-$d $H:$M:$S"; - $log_msg->{author} = $info->{'Last Changed Author'}; - $log_msg->{revision} = $committed; - $log_msg->{msg} .= "\n"; - $log_msg->{parents} = [ $last->{commit} ]; - $log_msg->{commit} = git_commit($log_msg, $commit); - return $log_msg; - } - # resync immediately - push @svn_up, "-r$last->{revision}"; - sys(@svn_up); - return fetch("$committed=$commit"); -} - sub rev_list_raw { - my (@args) = @_; - my $pid = open my $fh, '-|'; - defined $pid or croak $!; - if (!$pid) { - exec(qw/git-rev-list --pretty=raw/, @args) or croak $!; - } - return { fh => $fh, t => { } }; + my ($fh, $c) = command_output_pipe(qw/rev-list --pretty=raw/, @_); + return { fh => $fh, ctx => $c, t => { } }; } sub next_rev_list_entry { @@ -1749,184 +1288,10 @@ sub next_rev_list_entry { $x->{m} .= $_; } } + command_close_pipe($fh, $rl->{ctx}); return ($x != $rl->{t}) ? $x : undef; } -# read the entire log into a temporary file (which is removed ASAP) -# and store the file handle + parser state -sub svn_log_raw { - my (@log_args) = @_; - my $log_fh = IO::File->new_tmpfile or croak $!; - my $pid = fork; - defined $pid or croak $!; - if (!$pid) { - open STDOUT, '>&', $log_fh or croak $!; - exec (qw(svn log), @log_args) or croak $! - } - waitpid $pid, 0; - croak $? if $?; - seek $log_fh, 0, 0 or croak $!; - return { state => 'sep', fh => $log_fh }; -} - -sub next_log_entry { - my $log = shift; # retval of svn_log_raw() - my $ret = undef; - my $fh = $log->{fh}; - - while (<$fh>) { - chomp; - if (/^\-{72}$/) { - if ($log->{state} eq 'msg') { - if ($ret->{lines}) { - $ret->{msg} .= $_."\n"; - unless(--$ret->{lines}) { - $log->{state} = 'sep'; - } - } else { - croak "Log parse error at: $_\n", - $ret->{revision}, - "\n"; - } - next; - } - if ($log->{state} ne 'sep') { - croak "Log parse error at: $_\n", - "state: $log->{state}\n", - $ret->{revision}, - "\n"; - } - $log->{state} = 'rev'; - - # if we have an empty log message, put something there: - if ($ret) { - $ret->{msg} ||= "\n"; - delete $ret->{lines}; - return $ret; - } - next; - } - if ($log->{state} eq 'rev' && s/^r(\d+)\s*\|\s*//) { - my $rev = $1; - my ($author, $date, $lines) = split(/\s*\|\s*/, $_, 3); - ($lines) = ($lines =~ /(\d+)/); - $date = '1970-01-01 00:00:00 +0000' - if ($_ignore_nodate && $date eq '(no date)'); - my ($Y,$m,$d,$H,$M,$S,$tz) = ($date =~ - /(\d{4})\-(\d\d)\-(\d\d)\s - (\d\d)\:(\d\d)\:(\d\d)\s([\-\+]\d+)/x) - or croak "Failed to parse date: $date\n"; - $ret = { revision => $rev, - date => "$tz $Y-$m-$d $H:$M:$S", - author => $author, - lines => $lines, - msg => '' }; - if (defined $_authors && ! defined $users{$author}) { - die "Author: $author not defined in ", - "$_authors file\n"; - } - $log->{state} = 'msg_start'; - next; - } - # skip the first blank line of the message: - if ($log->{state} eq 'msg_start' && /^$/) { - $log->{state} = 'msg'; - } elsif ($log->{state} eq 'msg') { - if ($ret->{lines}) { - $ret->{msg} .= $_."\n"; - unless (--$ret->{lines}) { - $log->{state} = 'sep'; - } - } else { - croak "Log parse error at: $_\n", - $ret->{revision},"\n"; - } - } - } - return $ret; -} - -sub svn_info { - my $url = shift || $SVN_URL; - - my $pid = open my $info_fh, '-|'; - defined $pid or croak $!; - - if ($pid == 0) { - exec(qw(svn info),$url) or croak $!; - } - - my $ret = {}; - # only single-lines seem to exist in svn info output - while (<$info_fh>) { - chomp $_; - if (m#^([^:]+)\s*:\s*(\S.*)$#) { - $ret->{$1} = $2; - push @{$ret->{-order}}, $1; - } - } - close $info_fh or croak $?; - return $ret; -} - -sub sys { system(@_) == 0 or croak $? } - -sub do_update_index { - my ($z_cmd, $cmd, $no_text_base) = @_; - - my $z = open my $p, '-|'; - defined $z or croak $!; - unless ($z) { exec @$z_cmd or croak $! } - - my $pid = open my $ui, '|-'; - defined $pid or croak $!; - unless ($pid) { - exec('git-update-index',"--$cmd",'-z','--stdin') or croak $!; - } - local $/ = "\0"; - while (my $x = <$p>) { - chomp $x; - if (!$no_text_base && lstat $x && ! -l _ && - svn_propget_base('svn:keywords', $x)) { - my $mode = -x _ ? 0755 : 0644; - my ($v,$d,$f) = File::Spec->splitpath($x); - my $tb = File::Spec->catfile($d, '.svn', 'tmp', - 'text-base',"$f.svn-base"); - $tb =~ s#^/##; - unless (-f $tb) { - $tb = File::Spec->catfile($d, '.svn', - 'text-base',"$f.svn-base"); - $tb =~ s#^/##; - } - my @s = stat($x); - unlink $x or croak $!; - copy($tb, $x); - chmod(($mode &~ umask), $x) or croak $!; - utime $s[8], $s[9], $x; - } - print $ui $x,"\0"; - } - close $ui or croak $?; -} - -sub index_changes { - return if $_use_lib; - - if (!-f "$GIT_SVN_DIR/info/exclude") { - open my $fd, '>>', "$GIT_SVN_DIR/info/exclude" or croak $!; - print $fd '.svn',"\n"; - close $fd or croak $!; - } - my $no_text_base = shift; - do_update_index([qw/git-diff-files --name-only -z/], - 'remove', - $no_text_base); - do_update_index([qw/git-ls-files -z --others/, - "--exclude-from=$GIT_SVN_DIR/info/exclude"], - 'add', - $no_text_base); -} - sub s_to_file { my ($str, $file, $mode) = @_; open my $fd,'>',$file or croak $!; @@ -1952,18 +1317,6 @@ sub assert_revision_unknown { } } -sub trees_eq { - my ($x, $y) = @_; - my @x = safe_qx('git-cat-file','commit',$x); - my @y = safe_qx('git-cat-file','commit',$y); - if (($y[0] ne $x[0]) || $x[0] !~ /^tree $sha1\n$/ - || $y[0] !~ /^tree $sha1\n$/) { - print STDERR "Trees not equal: $y[0] != $x[0]\n"; - return 0 - } - return 1; -} - sub git_commit { my ($log_msg, @parents) = @_; assert_revision_unknown($log_msg->{revision}); @@ -1988,15 +1341,14 @@ sub git_commit { my $tree = $log_msg->{tree}; if (!defined $tree) { my $index = set_index($GIT_SVN_INDEX); - index_changes(); - chomp($tree = `git-write-tree`); + $tree = command_oneline('write-tree'); croak $? if $?; restore_index($index); } - # just in case we clobber the existing ref, we still want that ref # as our parent: - if (my $cur = eval { file_to_s("$GIT_DIR/refs/remotes/$GIT_SVN") }) { + if (my $cur = verify_ref("refs/remotes/$GIT_SVN^0")) { + chomp $cur; push @tmp_parents, $cur; } @@ -2005,9 +1357,7 @@ sub git_commit { my $skip; foreach (@tmp_parents) { # see if a common parent is found - my $mb = eval { - safe_qx('git-merge-base', $_, $p) - }; + my $mb = eval { command('merge-base', $_, $p) }; next if ($@ || $?); $skip = 1; last; @@ -2049,7 +1399,7 @@ sub git_commit { if ($commit !~ /^$sha1$/o) { die "Failed to commit, invalid sha1: $commit\n"; } - sys('git-update-ref',"refs/remotes/$GIT_SVN",$commit); + command_noisy('update-ref',"refs/remotes/$GIT_SVN",$commit); revdb_set($REVDB, $log_msg->{revision}, $commit); # this output is read via pipe, do not change: @@ -2061,7 +1411,8 @@ sub git_commit { sub check_repack { if ($_repack && (--$_repack_nr == 0)) { $_repack_nr = $_repack; - sys("git repack $_repack_flags"); + # repack doesn't use any arguments with spaces in them, does it? + command_noisy('repack', split(/\s+/, $_repack_flags)); } } @@ -2078,122 +1429,17 @@ sub set_commit_env { $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_msg->{date}; } -sub apply_mod_line_blob { - my $m = shift; - if ($m->{mode_b} =~ /^120/) { - blob_to_symlink($m->{sha1_b}, $m->{file_b}); - } else { - blob_to_file($m->{sha1_b}, $m->{file_b}); - } -} - -sub blob_to_symlink { - my ($blob, $link) = @_; - defined $link or croak "\$link not defined!\n"; - croak "Not a sha1: $blob\n" unless $blob =~ /^$sha1$/o; - if (-l $link || -f _) { - unlink $link or croak $!; - } - - my $dest = `git-cat-file blob $blob`; # no newline, so no chomp - symlink $dest, $link or croak $!; -} - -sub blob_to_file { - my ($blob, $file) = @_; - defined $file or croak "\$file not defined!\n"; - croak "Not a sha1: $blob\n" unless $blob =~ /^$sha1$/o; - if (-l $file || -f _) { - unlink $file or croak $!; - } - - open my $blob_fh, '>', $file or croak "$!: $file\n"; - my $pid = fork; - defined $pid or croak $!; - - if ($pid == 0) { - open STDOUT, '>&', $blob_fh or croak $!; - exec('git-cat-file','blob',$blob) or croak $!; - } - waitpid $pid, 0; - croak $? if $?; - - close $blob_fh or croak $!; -} - -sub safe_qx { - my $pid = open my $child, '-|'; - defined $pid or croak $!; - if ($pid == 0) { - exec(@_) or croak $!; - } - my @ret = (<$child>); - close $child or croak $?; - die $? if $?; # just in case close didn't error out - return wantarray ? @ret : join('',@ret); -} - -sub svn_compat_check { - if ($_follow_parent) { - print STDERR 'E: --follow-parent functionality is only ', - "available when SVN libraries are used\n"; - exit 1; - } - my @co_help = safe_qx(qw(svn co -h)); - unless (grep /ignore-externals/,@co_help) { - print STDERR "W: Installed svn version does not support ", - "--ignore-externals\n"; - $_no_ignore_ext = 1; - } - if (grep /usage: checkout URL\[\@REV\]/,@co_help) { - $_svn_co_url_revs = 1; - } - if (grep /\[TARGET\[\@REV\]\.\.\.\]/, `svn propget -h`) { - $_svn_pg_peg_revs = 1; - } - - # I really, really hope nobody hits this... - unless (grep /stop-on-copy/, (safe_qx(qw(svn log -h)))) { - print STDERR <<''; -W: The installed svn version does not support the --stop-on-copy flag in - the log command. - Lets hope the directory you're tracking is not a branch or tag - and was never moved within the repository... - - $_no_stop_copy = 1; - } -} - -# *sigh*, new versions of svn won't honor -r<rev> without URL@<rev>, -# (and they won't honor URL@<rev> without -r<rev>, too!) -sub svn_cmd_checkout { - my ($url, $rev, $dir) = @_; - my @cmd = ('svn','co', "-r$rev"); - push @cmd, '--ignore-externals' unless $_no_ignore_ext; - $url .= "\@$rev" if $_svn_co_url_revs; - sys(@cmd, $url, $dir); -} - sub check_upgrade_needed { if (!-r $REVDB) { -d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]); open my $fh, '>>',$REVDB or croak $!; close $fh; } - my $old = eval { - my $pid = open my $child, '-|'; - defined $pid or croak $!; - if ($pid == 0) { - close STDERR; - exec('git-rev-parse',"$GIT_SVN-HEAD") or croak $!; - } - my @ret = (<$child>); - close $child or croak $?; - die $? if $?; # just in case close didn't error out - return wantarray ? @ret : join('',@ret); + return unless eval { + command([qw/rev-parse --verify/,"$GIT_SVN-HEAD^0"], + {STDERR => 0}); }; - return unless $old; - my $head = eval { safe_qx('git-rev-parse',"refs/remotes/$GIT_SVN") }; + my $head = eval { command('rev-parse',"refs/remotes/$GIT_SVN") }; if ($@ || !$head) { print STDERR "Please run: $0 rebuild --upgrade\n"; exit 1; @@ -2205,12 +1451,8 @@ sub check_upgrade_needed { sub map_tree_joins { my %seen; foreach my $br (@_branch_from) { - my $pid = open my $pipe, '-|'; - defined $pid or croak $!; - if ($pid == 0) { - exec(qw(git-rev-list --topo-order --pretty=raw), $br) - or croak $!; - } + my $pipe = command_output_pipe(qw/rev-list + --topo-order --pretty=raw/, $br); while (<$pipe>) { if (/^commit ($sha1)$/o) { my $commit = $1; @@ -2226,7 +1468,7 @@ sub map_tree_joins { $seen{$commit} = 1; } } - close $pipe; # we could be breaking the pipe early + eval { command_close_pipe($pipe) }; } } @@ -2238,7 +1480,7 @@ sub load_all_refs { # don't worry about rev-list on non-commit objects/tags, # it shouldn't blow up if a ref is a blob or tree... - chomp(@_branch_from = `git-rev-parse --symbolic --all`); + @_branch_from = command(qw/rev-parse --symbolic --all/); } # '<svn username> = real-name <email address>' mapping based on git-svnimport: @@ -2264,15 +1506,9 @@ sub rload_authors { close $authors or croak $!; } -sub svn_propget_base { - my ($p, $f) = @_; - $f .= '@BASE' if $_svn_pg_peg_revs; - return safe_qx(qw/svn propget/, $p, $f); -} - sub git_svn_each { my $sub = shift; - foreach (`git-rev-parse --symbolic --all`) { + foreach (command(qw/rev-parse --symbolic --all/)) { next unless s#^refs/remotes/##; chomp $_; next unless -f "$GIT_DIR/svn/$_/info/url"; @@ -2313,7 +1549,7 @@ sub migration_check { "$GIT_SVN_DIR\n\t(required for this version ", "($VERSION) of git-svn) does not.\n"; - foreach my $x (`git-rev-parse --symbolic --all`) { + foreach my $x (command(qw/rev-parse --symbolic --all/)) { next unless $x =~ s#^refs/remotes/##; chomp $x; next unless -f "$GIT_DIR/$x/info/url"; @@ -2418,11 +1654,7 @@ sub write_grafts { my $p = $grafts->{$c}; my %x; # real parents delete $p->{$c}; # commits are not self-reproducing... - my $pid = open my $ch, '-|'; - defined $pid or croak $!; - if (!$pid) { - exec(qw/git-cat-file commit/, $c) or croak $!; - } + my $ch = command_output_pipe(qw/cat-file commit/, $c); while (<$ch>) { if (/^parent ($sha1)/) { $x{$1} = $p->{$1} = 1; @@ -2430,7 +1662,7 @@ sub write_grafts { last unless /^\S/; } } - close $ch; # breaking the pipe + eval { command_close_pipe($ch) }; # breaking the pipe # if real parents are the only ones in the grafts, drop it next if join(' ',sort keys %$p) eq join(' ',sort keys %x); @@ -2442,7 +1674,7 @@ sub write_grafts { next if $del{$i} || $p->{$i} == 2; foreach my $j (@jp) { next if $i eq $j || $del{$j} || $p->{$j} == 2; - $mb = eval { safe_qx('git-merge-base',$i,$j) }; + $mb = eval { command('merge-base', $i, $j) }; next unless $mb; chomp $mb; next if $x{$mb}; @@ -2513,15 +1745,12 @@ sub extract_metadata { sub cmt_metadata { return extract_metadata((grep(/^git-svn-id: /, - safe_qx(qw/git-cat-file commit/, shift)))[-1]); + command(qw/cat-file commit/, shift)))[-1]); } sub get_commit_time { my $cmt = shift; - defined(my $pid = open my $fh, '-|') or croak $!; - if (!$pid) { - exec qw/git-rev-list --pretty=raw -n1/, $cmt or croak $!; - } + my $fh = command_output_pipe(qw/rev-list --pretty=raw -n1/, $cmt); while (<$fh>) { /^committer\s(?:.+) (\d+) ([\-\+]?\d+)$/ or next; my ($s, $tz) = ($1, $2); @@ -2530,7 +1759,7 @@ sub get_commit_time { } elsif ($tz =~ s/^\-//) { $s -= tz_to_s_offset($tz); } - close $fh; + eval { command_close_pipe($fh) }; return $s; } die "Can't get commit time for commit: $cmt\n"; @@ -2542,14 +1771,18 @@ sub tz_to_s_offset { return ($1 * 60) + ($tz * 3600); } -sub setup_pager { # translated to Perl from pager.c - return unless (-t *STDOUT); - my $pager = $ENV{PAGER}; - if (!defined $pager) { - $pager = 'less'; - } elsif (length $pager == 0 || $pager eq 'cat') { - return; +# adapted from pager.c +sub config_pager { + $_pager ||= $ENV{GIT_PAGER} || $ENV{PAGER}; + if (!defined $_pager) { + $_pager = 'less'; + } elsif (length $_pager == 0 || $_pager eq 'cat') { + $_pager = undef; } +} + +sub run_pager { + return unless -t *STDOUT; pipe my $rfd, my $wfd or return; defined(my $pid = fork) or croak $!; if (!$pid) { @@ -2557,8 +1790,8 @@ sub setup_pager { # translated to Perl from pager.c return; } open STDIN, '<&', $rfd or croak $!; - $ENV{LESS} ||= '-S'; - exec $pager or croak "Can't run pager: $!\n";; + $ENV{LESS} ||= 'FRSX'; + exec $_pager or croak "Can't run pager: $! ($_pager)\n"; } sub get_author_info { @@ -2672,31 +1905,6 @@ sub show_commit_normal { } } -sub libsvn_load { - return unless $_use_lib; - $_use_lib = eval { - require SVN::Core; - if ($SVN::Core::VERSION lt '1.1.0') { - die "Need SVN::Core 1.1.0 or better ", - "(got $SVN::Core::VERSION) ", - "Falling back to command-line svn\n"; - } - require SVN::Ra; - require SVN::Delta; - push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor'; - my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file. - $SVN::Node::dir.$SVN::Node::unknown. - $SVN::Node::none.$SVN::Node::file. - $SVN::Node::dir.$SVN::Node::unknown. - $SVN::Auth::SSL::CNMISMATCH. - $SVN::Auth::SSL::NOTYETVALID. - $SVN::Auth::SSL::EXPIRED. - $SVN::Auth::SSL::UNKNOWNCA. - $SVN::Auth::SSL::OTHER; - 1; - }; -} - sub _simple_prompt { my ($cred, $realm, $default_username, $may_save, $pool) = @_; $may_save = undef if $_no_auth_cache; @@ -2815,34 +2023,68 @@ sub _read_password { sub libsvn_connect { my ($url) = @_; - if (!$AUTH_BATON || !$AUTH_CALLBACKS) { - SVN::_Core::svn_config_ensure($_config_dir, undef); - ($AUTH_BATON, $AUTH_CALLBACKS) = SVN::Core::auth_open_helper([ - SVN::Client::get_simple_provider(), - SVN::Client::get_ssl_server_trust_file_provider(), - SVN::Client::get_simple_prompt_provider( - \&_simple_prompt, 2), - SVN::Client::get_ssl_client_cert_prompt_provider( - \&_ssl_client_cert_prompt, 2), - SVN::Client::get_ssl_client_cert_pw_prompt_provider( - \&_ssl_client_cert_pw_prompt, 2), - SVN::Client::get_username_provider(), - SVN::Client::get_ssl_server_trust_prompt_provider( - \&_ssl_server_trust_prompt), - SVN::Client::get_username_prompt_provider( - \&_username_prompt, 2), - ]); - } - SVN::Ra->new(url => $url, auth => $AUTH_BATON, - auth_provider_callbacks => $AUTH_CALLBACKS); + SVN::_Core::svn_config_ensure($_config_dir, undef); + my ($baton, $callbacks) = SVN::Core::auth_open_helper([ + SVN::Client::get_simple_provider(), + SVN::Client::get_ssl_server_trust_file_provider(), + SVN::Client::get_simple_prompt_provider( + \&_simple_prompt, 2), + SVN::Client::get_ssl_client_cert_prompt_provider( + \&_ssl_client_cert_prompt, 2), + SVN::Client::get_ssl_client_cert_pw_prompt_provider( + \&_ssl_client_cert_pw_prompt, 2), + SVN::Client::get_username_provider(), + SVN::Client::get_ssl_server_trust_prompt_provider( + \&_ssl_server_trust_prompt), + SVN::Client::get_username_prompt_provider( + \&_username_prompt, 2), + ]); + my $config = SVN::Core::config_get_config($_config_dir); + my $ra = SVN::Ra->new(url => $url, auth => $baton, + config => $config, + pool => SVN::Pool->new, + auth_provider_callbacks => $callbacks); + + my $df = $ENV{GIT_SVN_DELTA_FETCH}; + if (defined $df) { + $_xfer_delta = $df; + } else { + $_xfer_delta = ($url =~ m#^file://#) ? undef : 1; + } + $ra->{svn_path} = $url; + $ra->{repos_root} = $ra->get_repos_root; + $ra->{svn_path} =~ s#^\Q$ra->{repos_root}\E/*##; + push @repo_path_split_cache, qr/^(\Q$ra->{repos_root}\E)/; + return $ra; } -sub libsvn_get_file { - my ($gui, $f, $rev, $chg) = @_; - my $p = $f; - if (length $SVN_PATH > 0) { - return unless ($p =~ s#^\Q$SVN_PATH\E/##); +sub libsvn_can_do_switch { + unless (defined $_svn_can_do_switch) { + my $pool = SVN::Pool->new; + my $rep = eval { + $SVN->do_switch(1, '', 0, $SVN->{url}, + SVN::Delta::Editor->new, $pool); + }; + if ($@) { + $_svn_can_do_switch = 0; + } else { + $rep->abort_report($pool); + $_svn_can_do_switch = 1; + } + $pool->clear; } + $_svn_can_do_switch; +} + +sub libsvn_dup_ra { + my ($ra) = @_; + SVN::Ra->new(map { $_ => $ra->{$_} } qw/config url + auth auth_provider_callbacks repos_root svn_path/); +} + +sub libsvn_get_file { + my ($gui, $f, $rev, $chg, $untracked) = @_; + $f =~ s#^/##; print "\t$chg\t$f\n" unless $_q; my ($hash, $pid, $in, $out); @@ -2866,7 +2108,7 @@ sub libsvn_get_file { my $mode = exists $props->{'svn:executable'} ? '100755' : '100644'; if (exists $props->{'svn:special'}) { $mode = '120000'; - my $link = `git-cat-file blob $hash`; + my $link = `git-cat-file blob $hash`; # no chomping symlinks $link =~ s/^link // or die "svn:special file with contents: <", $link, "> is not understood\n"; defined($pid = open3($in, $out, '>&STDERR', @@ -2879,89 +2121,200 @@ sub libsvn_get_file { waitpid $pid, 0; $hash =~ /^$sha1$/o or die "not a sha1: $hash\n"; } - print $gui $mode,' ',$hash,"\t",$p,"\0" or croak $!; + %{$untracked->{file_prop}->{$f}} = %$props; + print $gui $mode,' ',$hash,"\t",$f,"\0" or croak $!; +} + +sub uri_encode { + my ($f) = @_; + $f =~ s#([^a-zA-Z0-9\*!\:_\./\-])#uc sprintf("%%%02x",ord($1))#eg; + $f +} + +sub uri_decode { + my ($f) = @_; + $f =~ tr/+/ /; + $f =~ s/%([A-F0-9]{2})/chr hex($1)/ge; + $f } sub libsvn_log_entry { - my ($rev, $author, $date, $msg, $parents) = @_; + my ($rev, $author, $date, $msg, $parents, $untracked) = @_; my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T (\d\d)\:(\d\d)\:(\d\d).\d+Z$/x) or die "Unable to parse date: $date\n"; - if (defined $_authors && ! defined $users{$author}) { + if (defined $author && length $author > 0 && + defined $_authors && ! defined $users{$author}) { die "Author: $author not defined in $_authors file\n"; } $msg = '' if ($rev == 0 && !defined $msg); - return { revision => $rev, date => "+0000 $Y-$m-$d $H:$M:$S", - author => $author, msg => $msg."\n", parents => $parents || [] } + + open my $un, '>>', "$GIT_SVN_DIR/unhandled.log" or croak $!; + my $h; + print $un "r$rev\n" or croak $!; + $h = $untracked->{empty}; + foreach (sort keys %$h) { + my $act = $h->{$_} ? '+empty_dir' : '-empty_dir'; + print $un " $act: ", uri_encode($_), "\n" or croak $!; + warn "W: $act: $_\n"; + } + foreach my $t (qw/dir_prop file_prop/) { + $h = $untracked->{$t} or next; + foreach my $path (sort keys %$h) { + my $ppath = $path eq '' ? '.' : $path; + foreach my $prop (sort keys %{$h->{$path}}) { + next if $SKIP{$prop}; + my $v = $h->{$path}->{$prop}; + if (defined $v) { + print $un " +$t: ", + uri_encode($ppath), ' ', + uri_encode($prop), ' ', + uri_encode($v), "\n" + or croak $!; + } else { + print $un " -$t: ", + uri_encode($ppath), ' ', + uri_encode($prop), "\n" + or croak $!; + } + } + } + } + foreach my $t (qw/absent_file absent_directory/) { + $h = $untracked->{$t} or next; + foreach my $parent (sort keys %$h) { + foreach my $path (sort @{$h->{$parent}}) { + print $un " $t: ", + uri_encode("$parent/$path"), "\n" + or croak $!; + warn "W: $t: $parent/$path ", + "Insufficient permissions?\n"; + } + } + } + + # revprops (make this optional? it's an extra network trip...) + my $pool = SVN::Pool->new; + my $rp = $SVN->rev_proplist($rev, $pool); + foreach (sort keys %$rp) { + next if /^svn:(?:author|date|log)$/; + print $un " rev_prop: ", uri_encode($_), ' ', + uri_encode($rp->{$_}), "\n"; + } + $pool->clear; + close $un or croak $!; + + { revision => $rev, date => "+0000 $Y-$m-$d $H:$M:$S", + author => $author, msg => $msg."\n", parents => $parents || [], + revprops => $rp } } sub process_rm { - my ($gui, $last_commit, $f) = @_; - $f =~ s#^\Q$SVN_PATH\E/?## or return; + my ($gui, $last_commit, $f, $q) = @_; # remove entire directories. - if (safe_qx('git-ls-tree',$last_commit,'--',$f) =~ /^040000 tree/) { - defined(my $pid = open my $ls, '-|') or croak $!; - if (!$pid) { - exec(qw/git-ls-tree -r --name-only -z/, - $last_commit,'--',$f) or croak $!; - } + if (command('ls-tree',$last_commit,'--',$f) =~ /^040000 tree/) { + my ($ls, $ctx) = command_output_pipe(qw/ls-tree + -r --name-only -z/, + $last_commit,'--',$f); local $/ = "\0"; while (<$ls>) { print $gui '0 ',0 x 40,"\t",$_ or croak $!; + print "\tD\t$_\n" unless $q; } - close $ls or croak $?; + print "\tD\t$f/\n" unless $q; + command_close_pipe($ls, $ctx); + return $SVN::Node::dir; } else { print $gui '0 ',0 x 40,"\t",$f,"\0" or croak $!; + print "\tD\t$f\n" unless $q; + return $SVN::Node::file; } } sub libsvn_fetch { + $_xfer_delta ? libsvn_fetch_delta(@_) : libsvn_fetch_full(@_); +} + +sub libsvn_fetch_delta { + my ($last_commit, $paths, $rev, $author, $date, $msg) = @_; + my $pool = SVN::Pool->new; + my $ed = SVN::Git::Fetcher->new({ c => $last_commit, q => $_q }); + my $reporter = $SVN->do_update($rev, '', 1, $ed, $pool); + my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); + my (undef, $last_rev, undef) = cmt_metadata($last_commit); + $reporter->set_path('', $last_rev, 0, @lock, $pool); + $reporter->finish_report($pool); + $pool->clear; + unless ($ed->{git_commit_ok}) { + die "SVN connection failed somewhere...\n"; + } + libsvn_log_entry($rev, $author, $date, $msg, [$last_commit], $ed); +} + +sub libsvn_fetch_full { my ($last_commit, $paths, $rev, $author, $date, $msg) = @_; - open my $gui, '| git-update-index -z --index-info' or croak $!; - my @amr; + my ($gui, $ctx) = command_input_pipe(qw/update-index -z --index-info/); + my %amr; + my $ut = { empty => {}, dir_prop => {}, file_prop => {} }; + my $p = $SVN->{svn_path}; foreach my $f (keys %$paths) { my $m = $paths->{$f}->action(); - $f =~ s#^/+##; + if (length $p) { + $f =~ s#^/\Q$p\E/##; + next if $f =~ m#^/#; + } else { + $f =~ s#^/##; + } if ($m =~ /^[DR]$/) { - print "\t$m\t$f\n" unless $_q; - process_rm($gui, $last_commit, $f); - next if $m eq 'D'; + my $t = process_rm($gui, $last_commit, $f, $_q); + if ($m eq 'D') { + $ut->{empty}->{$f} = 0 if $t == $SVN::Node::dir; + next; + } # 'R' can be file replacements, too, right? } my $pool = SVN::Pool->new; my $t = $SVN->check_path($f, $rev, $pool); if ($t == $SVN::Node::file) { if ($m =~ /^[AMR]$/) { - push @amr, [ $m, $f ]; + $amr{$f} = $m; } else { die "Unrecognized action: $m, ($f r$rev)\n"; } } elsif ($t == $SVN::Node::dir && $m =~ /^[AR]$/) { my @traversed = (); - libsvn_traverse($gui, '', $f, $rev, \@traversed); - foreach (@traversed) { - push @amr, [ $m, $_ ] + libsvn_traverse($gui, '', $f, $rev, \@traversed, $ut); + if (@traversed) { + foreach (@traversed) { + $amr{$_} = $m; + } + } else { + my ($dir, $file) = ($f =~ m#^(.*?)/?([^/]+)$#); + delete $ut->{empty}->{$dir}; + $ut->{empty}->{$f} = 1; } } $pool->clear; } - foreach (@amr) { - libsvn_get_file($gui, $_->[1], $rev, $_->[0]); + foreach (keys %amr) { + libsvn_get_file($gui, $_, $rev, $amr{$_}, $ut); + my ($d) = ($_ =~ m#^(.*?)/?(?:[^/]+)$#); + delete $ut->{empty}->{$d}; + } + unless (exists $ut->{dir_prop}->{''}) { + my $pool = SVN::Pool->new; + my (undef, undef, $props) = $SVN->get_dir('', $rev, $pool); + %{$ut->{dir_prop}->{''}} = %$props; + $pool->clear; } - close $gui or croak $?; - return libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]); + command_close_pipe($gui, $ctx); + libsvn_log_entry($rev, $author, $date, $msg, [$last_commit], $ut); } sub svn_grab_base_rev { - defined(my $pid = open my $fh, '-|') or croak $!; - if (!$pid) { - open my $null, '>', '/dev/null' or croak $!; - open STDERR, '>&', $null or croak $!; - exec qw/git-rev-parse --verify/,"refs/remotes/$GIT_SVN^0" - or croak $!; - } - chomp(my $c = do { local $/; <$fh> }); - close $fh; + my $c = eval { command_oneline([qw/rev-parse --verify/, + "refs/remotes/$GIT_SVN^0"], + { STDERR => 0 }) }; if (defined $c && length $c) { my ($url, $rev, $uuid) = cmt_metadata($c); return ($rev, $c) if defined $rev; @@ -3010,25 +2363,38 @@ sub libsvn_parse_revision { } sub libsvn_traverse { - my ($gui, $pfx, $path, $rev, $files) = @_; - my $cwd = "$pfx/$path"; + my ($gui, $pfx, $path, $rev, $files, $untracked) = @_; + my $cwd = length $pfx ? "$pfx/$path" : $path; my $pool = SVN::Pool->new; - $cwd =~ s#^/+##g; + $cwd =~ s#^\Q$SVN->{svn_path}\E##; + my $nr = 0; my ($dirent, $r, $props) = $SVN->get_dir($cwd, $rev, $pool); + %{$untracked->{dir_prop}->{$cwd}} = %$props; foreach my $d (keys %$dirent) { my $t = $dirent->{$d}->kind; if ($t == $SVN::Node::dir) { - libsvn_traverse($gui, $cwd, $d, $rev, $files); + my $i = libsvn_traverse($gui, $cwd, $d, $rev, + $files, $untracked); + if ($i) { + $nr += $i; + } else { + $untracked->{empty}->{"$cwd/$d"} = 1; + } } elsif ($t == $SVN::Node::file) { + $nr++; my $file = "$cwd/$d"; if (defined $files) { push @$files, $file; } else { - libsvn_get_file($gui, $file, $rev, 'A'); + libsvn_get_file($gui, $file, $rev, 'A', + $untracked); + my ($dir) = ($file =~ m#^(.*?)/?(?:[^/]+)$#); + delete $untracked->{empty}->{$dir}; } } } $pool->clear; + $nr; } sub libsvn_traverse_ignore { @@ -3037,7 +2403,7 @@ sub libsvn_traverse_ignore { my $pool = SVN::Pool->new; my ($dirent, undef, $props) = $SVN->get_dir($path, $r, $pool); my $p = $path; - $p =~ s#^\Q$SVN_PATH\E/?##; + $p =~ s#^\Q$SVN->{svn_path}\E/##; print $fh length $p ? "\n# $p\n" : "\n# /\n"; if (my $s = $props->{'svn:ignore'}) { $s =~ s/[\r\n]+/\n/g; @@ -3061,25 +2427,18 @@ sub revisions_eq { my ($path, $r0, $r1) = @_; return 1 if $r0 == $r1; my $nr = 0; - if ($_use_lib) { - # should be OK to use Pool here (r1 - r0) should be small - my $pool = SVN::Pool->new; - libsvn_get_log($SVN, "/$path", $r0, $r1, - 0, 1, 1, sub {$nr++}, $pool); - $pool->clear; - } else { - my ($url, undef) = repo_path_split($SVN_URL); - my $svn_log = svn_log_raw("$url/$path","-r$r0:$r1"); - while (next_log_entry($svn_log)) { $nr++ } - close $svn_log->{fh}; - } + # should be OK to use Pool here (r1 - r0) should be small + my $pool = SVN::Pool->new; + libsvn_get_log($SVN, [$path], $r0, $r1, + 0, 0, 1, sub {$nr++}, $pool); + $pool->clear; return 0 if ($nr > 1); return 1; } sub libsvn_find_parent_branch { my ($paths, $rev, $author, $date, $msg) = @_; - my $svn_path = '/'.$SVN_PATH; + my $svn_path = '/'.$SVN->{svn_path}; # look for a parent from another branch: my $i = $paths->{$svn_path} or return; @@ -3090,7 +2449,7 @@ sub libsvn_find_parent_branch { $branch_from =~ s#^/##; my $l_map = {}; read_url_paths_all($l_map, '', "$GIT_DIR/svn"); - my $url = $SVN->{url}; + my $url = $SVN->{repos_root}; defined $l_map->{$url} or return; my $id = $l_map->{$url}->{$branch_from}; if (!defined $id && $_follow_parent) { @@ -3112,7 +2471,7 @@ sub libsvn_find_parent_branch { $GIT_SVN = $ENV{GIT_SVN_ID} = $id; init_vars(); $SVN_URL = "$url/$branch_from"; - $SVN_LOG = $SVN = undef; + $SVN = undef; setup_git_svn(); # we can't assume SVN_URL exists at r+1: $_revision = "0:$r"; @@ -3127,9 +2486,27 @@ sub libsvn_find_parent_branch { if (revisions_eq($branch_from, $r0, $r)) { unlink $GIT_SVN_INDEX; print STDERR "Found branch parent: ($GIT_SVN) $parent\n"; - sys(qw/git-read-tree/, $parent); - return libsvn_fetch($parent, $paths, $rev, - $author, $date, $msg); + command_noisy('read-tree', $parent); + unless (libsvn_can_do_switch()) { + return libsvn_fetch_full($parent, $paths, $rev, + $author, $date, $msg); + } + # do_switch works with svn/trunk >= r22312, but that is not + # included with SVN 1.4.2 (the latest version at the moment), + # so we can't rely on it. + my $ra = libsvn_connect("$url/$branch_from"); + my $ed = SVN::Git::Fetcher->new({c => $parent, q => $_q }); + my $pool = SVN::Pool->new; + my $reporter = $ra->do_switch($rev, '', 1, $SVN->{url}, + $ed, $pool); + my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); + $reporter->set_path('', $r0, 0, @lock, $pool); + $reporter->finish_report($pool); + $pool->clear; + unless ($ed->{git_commit_ok}) { + die "SVN connection failed somewhere...\n"; + } + return libsvn_log_entry($rev, $author, $date, $msg, [$parent]); } print STDERR "Nope, branch point not imported or unknown\n"; return undef; @@ -3137,6 +2514,7 @@ sub libsvn_find_parent_branch { sub libsvn_get_log { my ($ra, @args) = @_; + $args[4]-- if $args[4] && $_xfer_delta && ! $_follow_parent; if ($SVN::Core::VERSION le '1.2.0') { splice(@args, 3, 1); } @@ -3148,10 +2526,27 @@ sub libsvn_new_tree { return $log_entry; } my ($paths, $rev, $author, $date, $msg) = @_; - open my $gui, '| git-update-index -z --index-info' or croak $!; - libsvn_traverse($gui, '', $SVN_PATH, $rev); - close $gui or croak $?; - return libsvn_log_entry($rev, $author, $date, $msg); + my $ut; + if ($_xfer_delta) { + my $pool = SVN::Pool->new; + my $ed = SVN::Git::Fetcher->new({q => $_q}); + my $reporter = $SVN->do_update($rev, '', 1, $ed, $pool); + my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); + $reporter->set_path('', $rev, 1, @lock, $pool); + $reporter->finish_report($pool); + $pool->clear; + unless ($ed->{git_commit_ok}) { + die "SVN connection failed somewhere...\n"; + } + $ut = $ed; + } else { + $ut = { empty => {}, dir_prop => {}, file_prop => {} }; + my ($gui, $ctx) = command_input_pipe(qw/update-index + -z --index-info/); + libsvn_traverse($gui, '', $SVN->{svn_path}, $rev, undef, $ut); + command_close_pipe($gui, $ctx); + } + libsvn_log_entry($rev, $author, $date, $msg, [], $ut); } sub find_graft_path_commit { @@ -3221,7 +2616,7 @@ sub libsvn_commit_cb { my $log = libsvn_log_entry($rev,$committer,$date,$msg); $log->{tree} = get_tree_from_treeish($c); my $cmt = git_commit($log, $cmt_last, $c); - my @diff = safe_qx('git-diff-tree', $cmt, $c); + my @diff = command('diff-tree', $cmt, $c); if (@diff) { print STDERR "Trees differ: $cmt $c\n", join('',@diff),"\n"; @@ -3234,12 +2629,11 @@ sub libsvn_commit_cb { sub libsvn_ls_fullurl { my $fullurl = shift; - my ($repo, $path) = repo_path_split($fullurl); - $SVN ||= libsvn_connect($repo); + my $ra = libsvn_connect($fullurl); my @ret; my $pool = SVN::Pool->new; - my ($dirent, undef, undef) = $SVN->get_dir($path, - $SVN->get_latest_revnum, $pool); + my $r = defined $_revision ? $_revision : $ra->get_latest_revnum; + my ($dirent, undef, undef) = $ra->get_dir('', $r, $pool); foreach my $d (keys %$dirent) { if ($dirent->{$d}->kind == $SVN::Node::dir) { push @ret, "$d/"; # add '/' for compat with cli svn @@ -3260,8 +2654,9 @@ sub libsvn_skip_unknown_revs { # Wonderfully consistent library, eh? # 160013 - svn:// and file:// # 175002 - http(s):// + # 175007 - http(s):// (this repo required authorization, too...) # More codes may be discovered later... - if ($errno == 175002 || $errno == 160013) { + if ($errno == 175007 || $errno == 175002 || $errno == 160013) { return; } croak "Error from SVN, ($errno): ", $err->expanded_message,"\n"; @@ -3313,20 +2708,225 @@ sub revdb_get { sub copy_remote_ref { my $origin = $_cp_remote ? $_cp_remote : 'origin'; my $ref = "refs/remotes/$GIT_SVN"; - if (safe_qx('git-ls-remote', $origin, $ref)) { - sys(qw/git fetch/, $origin, "$ref:$ref"); + if (command('ls-remote', $origin, $ref)) { + command_noisy('fetch', $origin, "$ref:$ref"); } elsif ($_cp_remote && !$_upgrade) { die "Unable to find remote reference: ", "refs/remotes/$GIT_SVN on $origin\n"; } } +{ + my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file. + $SVN::Node::dir.$SVN::Node::unknown. + $SVN::Node::none.$SVN::Node::file. + $SVN::Node::dir.$SVN::Node::unknown. + $SVN::Auth::SSL::CNMISMATCH. + $SVN::Auth::SSL::NOTYETVALID. + $SVN::Auth::SSL::EXPIRED. + $SVN::Auth::SSL::UNKNOWNCA. + $SVN::Auth::SSL::OTHER; +} + +package SVN::Git::Fetcher; +use vars qw/@ISA/; +use strict; +use warnings; +use Carp qw/croak/; +use IO::File qw//; +use Git qw/command command_oneline command_noisy + command_output_pipe command_input_pipe command_close_pipe/; + +# file baton members: path, mode_a, mode_b, pool, fh, blob, base +sub new { + my ($class, $git_svn) = @_; + my $self = SVN::Delta::Editor->new; + bless $self, $class; + $self->{c} = $git_svn->{c} if exists $git_svn->{c}; + $self->{q} = $git_svn->{q}; + $self->{empty} = {}; + $self->{dir_prop} = {}; + $self->{file_prop} = {}; + $self->{absent_dir} = {}; + $self->{absent_file} = {}; + ($self->{gui}, $self->{ctx}) = command_input_pipe( + qw/update-index -z --index-info/); + require Digest::MD5; + $self; +} + +sub open_root { + { path => '' }; +} + +sub open_directory { + my ($self, $path, $pb, $rev) = @_; + { path => $path }; +} + +sub delete_entry { + my ($self, $path, $rev, $pb) = @_; + my $t = process_rm($self->{gui}, $self->{c}, $path, $self->{q}); + $self->{empty}->{$path} = 0 if $t == $SVN::Node::dir; + undef; +} + +sub open_file { + my ($self, $path, $pb, $rev) = @_; + my ($mode, $blob) = (command('ls-tree', $self->{c}, '--',$path) + =~ /^(\d{6}) blob ([a-f\d]{40})\t/); + unless (defined $mode && defined $blob) { + die "$path was not found in commit $self->{c} (r$rev)\n"; + } + { path => $path, mode_a => $mode, mode_b => $mode, blob => $blob, + pool => SVN::Pool->new, action => 'M' }; +} + +sub add_file { + my ($self, $path, $pb, $cp_path, $cp_rev) = @_; + my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#); + delete $self->{empty}->{$dir}; + { path => $path, mode_a => 100644, mode_b => 100644, + pool => SVN::Pool->new, action => 'A' }; +} + +sub add_directory { + my ($self, $path, $cp_path, $cp_rev) = @_; + my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#); + delete $self->{empty}->{$dir}; + $self->{empty}->{$path} = 1; + { path => $path }; +} + +sub change_dir_prop { + my ($self, $db, $prop, $value) = @_; + $self->{dir_prop}->{$db->{path}} ||= {}; + $self->{dir_prop}->{$db->{path}}->{$prop} = $value; + undef; +} + +sub absent_directory { + my ($self, $path, $pb) = @_; + $self->{absent_dir}->{$pb->{path}} ||= []; + push @{$self->{absent_dir}->{$pb->{path}}}, $path; + undef; +} + +sub absent_file { + my ($self, $path, $pb) = @_; + $self->{absent_file}->{$pb->{path}} ||= []; + push @{$self->{absent_file}->{$pb->{path}}}, $path; + undef; +} + +sub change_file_prop { + my ($self, $fb, $prop, $value) = @_; + if ($prop eq 'svn:executable') { + if ($fb->{mode_b} != 120000) { + $fb->{mode_b} = defined $value ? 100755 : 100644; + } + } elsif ($prop eq 'svn:special') { + $fb->{mode_b} = defined $value ? 120000 : 100644; + } else { + $self->{file_prop}->{$fb->{path}} ||= {}; + $self->{file_prop}->{$fb->{path}}->{$prop} = $value; + } + undef; +} + +sub apply_textdelta { + my ($self, $fb, $exp) = @_; + my $fh = IO::File->new_tmpfile; + $fh->autoflush(1); + # $fh gets auto-closed() by SVN::TxDelta::apply(), + # (but $base does not,) so dup() it for reading in close_file + open my $dup, '<&', $fh or croak $!; + 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 $?; + + if (defined $exp) { + seek $base, 0, 0 or croak $!; + my $md5 = Digest::MD5->new; + $md5->addfile($base); + my $got = $md5->hexdigest; + die "Checksum mismatch: $fb->{path} $fb->{blob}\n", + "expected: $exp\n", + " got: $got\n" if ($got ne $exp); + } + } + seek $base, 0, 0 or croak $!; + $fb->{fh} = $dup; + $fb->{base} = $base; + [ SVN::TxDelta::apply($base, $fh, undef, $fb->{path}, $fb->{pool}) ]; +} + +sub close_file { + my ($self, $fb, $exp) = @_; + my $hash; + my $path = $fb->{path}; + if (my $fh = $fb->{fh}) { + seek($fh, 0, 0) or croak $!; + my $md5 = Digest::MD5->new; + $md5->addfile($fh); + my $got = $md5->hexdigest; + die "Checksum mismatch: $path\n", + "expected: $exp\n got: $got\n" if ($got ne $exp); + seek($fh, 0, 0) or croak $!; + if ($fb->{mode_b} == 120000) { + read($fh, my $buf, 5) == 5 or croak $!; + $buf eq 'link ' or die "$path has mode 120000", + "but is not a link\n"; + } + 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 $!; + } + chomp($hash = do { local $/; <$out> }); + close $out or croak $!; + close $fh or croak $!; + $hash =~ /^[a-f\d]{40}$/ or die "not a sha1: $hash\n"; + close $fb->{base} or croak $!; + } else { + $hash = $fb->{blob} or die "no blob information\n"; + } + $fb->{pool}->clear; + my $gui = $self->{gui}; + print $gui "$fb->{mode_b} $hash\t$path\0" or croak $!; + print "\t$fb->{action}\t$path\n" if $fb->{action} && ! $self->{q}; + undef; +} + +sub abort_edit { + my $self = shift; + eval { command_close_pipe($self->{gui}, $self->{ctx}) }; + $self->SUPER::abort_edit(@_); +} + +sub close_edit { + my $self = shift; + command_close_pipe($self->{gui}, $self->{ctx}); + $self->{git_commit_ok} = 1; + $self->SUPER::close_edit(@_); +} + package SVN::Git::Editor; use vars qw/@ISA/; use strict; use warnings; use Carp qw/croak/; use IO::File; +use Git qw/command command_oneline command_noisy + command_output_pipe command_input_pipe command_close_pipe/; sub new { my $class = shift; @@ -3349,8 +2949,7 @@ sub split_path { } sub repo_path { - (defined $_[1] && length $_[1]) ? "$_[0]->{svn_path}/$_[1]" - : $_[0]->{svn_path} + (defined $_[1] && length $_[1]) ? $_[1] : '' } sub url_path { @@ -3377,24 +2976,21 @@ sub rmdirs { delete $rm->{''}; # we never delete the url we're tracking return unless %$rm; - defined(my $pid = open my $fh,'-|') or croak $!; - if (!$pid) { - exec qw/git-ls-tree --name-only -r -z/, $self->{c} or croak $!; - } + my ($fh, $ctx) = command_output_pipe( + qw/ls-tree --name-only -r -z/, $self->{c}); local $/ = "\0"; - my @svn_path = split m#/#, $self->{svn_path}; while (<$fh>) { chomp; - my @dn = (@svn_path, (split m#/#, $_)); + my @dn = split m#/#, $_; while (pop @dn) { delete $rm->{join '/', @dn}; } unless (%$rm) { - close $fh; + eval { command_close_pipe($fh) }; return; } } - close $fh; + command_close_pipe($fh, $ctx); my ($r, $p, $bat) = ($self->{r}, $self->{pool}, $self->{bat}); foreach my $d (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$rm) { @@ -3569,13 +3165,7 @@ __END__ Data structures: -$svn_log hashref (as returned by svn_log_raw) -{ - fh => file handle of the log file, - state => state of the log file parser (sep/msg/rev/msg_start...) -} - -$log_msg hashref as returned by next_log_entry($svn_log) +$log_msg hashref as returned by libsvn_log_entry() { msg => 'whitespace-formatted log entry ', # trailing newline is preserved @@ -3584,7 +3174,6 @@ $log_msg hashref as returned by next_log_entry($svn_log) author => 'committer name' }; - @mods = array of diff-index line hashes, each element represents one line of diff-index output diff --git a/git-tag.sh b/git-tag.sh index ac269e3277..e1bfa82f1e 100755 --- a/git-tag.sh +++ b/git-tag.sh @@ -5,6 +5,7 @@ USAGE='-l [<pattern>] | [-a | -s | -u <key-id>] [-f | -d] [-m <msg>] <tagname> [ SUBDIRECTORY_OK='Yes' . git-sh-setup +message_given= annotate= signed= force= @@ -37,6 +38,21 @@ do annotate=1 shift message="$1" + if test "$#" = "0"; then + die "error: option -m needs an argument" + else + message_given=1 + fi + ;; + -F) + annotate=1 + shift + if test "$#" = "0"; then + die "error: option -F needs an argument" + else + message="$(cat "$1")" + message_given=1 + fi ;; -u) annotate=1 @@ -83,7 +99,7 @@ tagger=$(git-var GIT_COMMITTER_IDENT) || exit 1 trap 'rm -f "$GIT_DIR"/TAG_TMP* "$GIT_DIR"/TAG_FINALMSG "$GIT_DIR"/TAG_EDITMSG' 0 if [ "$annotate" ]; then - if [ -z "$message" ]; then + if [ -z "$message_given" ]; then ( echo "#" echo "# Write a tag message" echo "#" ) > "$GIT_DIR"/TAG_EDITMSG @@ -95,7 +111,7 @@ if [ "$annotate" ]; then grep -v '^#' <"$GIT_DIR"/TAG_EDITMSG | git-stripspace >"$GIT_DIR"/TAG_FINALMSG - [ -s "$GIT_DIR"/TAG_FINALMSG ] || { + [ -s "$GIT_DIR"/TAG_FINALMSG -o -n "$message_given" ] || { echo >&2 "No tag message?" exit 1 } @@ -1,20 +1,8 @@ -#include <stdio.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <dirent.h> -#include <unistd.h> -#include <stdlib.h> -#include <string.h> -#include <errno.h> -#include <limits.h> -#include <stdarg.h> -#include "git-compat-util.h" +#include "builtin.h" #include "exec_cmd.h" #include "cache.h" #include "quote.h" -#include "builtin.h" - const char git_usage_string[] = "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS]"; @@ -71,8 +59,10 @@ static int handle_options(const char*** argv, int* argc) } else if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) { setup_pager(); } else if (!strcmp(cmd, "--git-dir")) { - if (*argc < 1) - return -1; + if (*argc < 2) { + fprintf(stderr, "No directory given for --git-dir.\n" ); + usage(git_usage_string); + } setenv("GIT_DIR", (*argv)[1], 1); (*argv)++; (*argc)--; @@ -247,6 +237,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "ls-tree", cmd_ls_tree, RUN_SETUP }, { "mailinfo", cmd_mailinfo }, { "mailsplit", cmd_mailsplit }, + { "merge-file", cmd_merge_file }, { "mv", cmd_mv, RUN_SETUP }, { "name-rev", cmd_name_rev, RUN_SETUP }, { "pack-objects", cmd_pack_objects, RUN_SETUP }, @@ -255,11 +246,14 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "prune-packed", cmd_prune_packed, RUN_SETUP }, { "push", cmd_push, RUN_SETUP }, { "read-tree", cmd_read_tree, RUN_SETUP }, + { "reflog", cmd_reflog, RUN_SETUP }, { "repo-config", cmd_repo_config }, + { "rerere", cmd_rerere, RUN_SETUP }, { "rev-list", cmd_rev_list, RUN_SETUP }, { "rev-parse", cmd_rev_parse, RUN_SETUP }, { "rm", cmd_rm, RUN_SETUP }, { "runstatus", cmd_runstatus, RUN_SETUP }, + { "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER }, { "show-branch", cmd_show_branch, RUN_SETUP }, { "show", cmd_show, RUN_SETUP | USE_PAGER }, { "stripspace", cmd_stripspace }, diff --git a/git.spec.in b/git.spec.in index 83268fc9d9..fb95e37594 100644 --- a/git.spec.in +++ b/git.spec.in @@ -24,7 +24,7 @@ This is a dummy package which brings in all subpackages. %package core Summary: Core git tools Group: Development/Tools -Requires: zlib >= 1.2, rsync, rcs, curl, less, openssh-clients, python >= 2.3, expat +Requires: zlib >= 1.2, rsync, curl, less, openssh-clients, expat %description core This is a stupid (but extremely fast) directory content manager. It doesn't do a whole lot, but what it _does_ do is track directory diff --git a/gitMergeCommon.py b/gitMergeCommon.py deleted file mode 100644 index fdbf9e4778..0000000000 --- a/gitMergeCommon.py +++ /dev/null @@ -1,275 +0,0 @@ -# -# Copyright (C) 2005 Fredrik Kuivinen -# - -import sys, re, os, traceback -from sets import Set - -def die(*args): - printList(args, sys.stderr) - sys.exit(2) - -def printList(list, file=sys.stdout): - for x in list: - file.write(str(x)) - file.write(' ') - file.write('\n') - -import subprocess - -# Debugging machinery -# ------------------- - -DEBUG = 0 -functionsToDebug = Set() - -def addDebug(func): - if type(func) == str: - functionsToDebug.add(func) - else: - functionsToDebug.add(func.func_name) - -def debug(*args): - if DEBUG: - funcName = traceback.extract_stack()[-2][2] - if funcName in functionsToDebug: - printList(args) - -# Program execution -# ----------------- - -class ProgramError(Exception): - def __init__(self, progStr, error): - self.progStr = progStr - self.error = error - - def __str__(self): - return self.progStr + ': ' + self.error - -addDebug('runProgram') -def runProgram(prog, input=None, returnCode=False, env=None, pipeOutput=True): - debug('runProgram prog:', str(prog), 'input:', str(input)) - if type(prog) is str: - progStr = prog - else: - progStr = ' '.join(prog) - - try: - if pipeOutput: - stderr = subprocess.STDOUT - stdout = subprocess.PIPE - else: - stderr = None - stdout = None - pop = subprocess.Popen(prog, - shell = type(prog) is str, - stderr=stderr, - stdout=stdout, - stdin=subprocess.PIPE, - env=env) - except OSError, e: - debug('strerror:', e.strerror) - raise ProgramError(progStr, e.strerror) - - if input != None: - pop.stdin.write(input) - pop.stdin.close() - - if pipeOutput: - out = pop.stdout.read() - else: - out = '' - - code = pop.wait() - if returnCode: - ret = [out, code] - else: - ret = out - if code != 0 and not returnCode: - debug('error output:', out) - debug('prog:', prog) - raise ProgramError(progStr, out) -# debug('output:', out.replace('\0', '\n')) - return ret - -# Code for computing common ancestors -# ----------------------------------- - -currentId = 0 -def getUniqueId(): - global currentId - currentId += 1 - return currentId - -# The 'virtual' commit objects have SHAs which are integers -shaRE = re.compile('^[0-9a-f]{40}$') -def isSha(obj): - return (type(obj) is str and bool(shaRE.match(obj))) or \ - (type(obj) is int and obj >= 1) - -class Commit(object): - __slots__ = ['parents', 'firstLineMsg', 'children', '_tree', 'sha', - 'virtual'] - - def __init__(self, sha, parents, tree=None): - self.parents = parents - self.firstLineMsg = None - self.children = [] - - if tree: - tree = tree.rstrip() - assert(isSha(tree)) - self._tree = tree - - if not sha: - self.sha = getUniqueId() - self.virtual = True - self.firstLineMsg = 'virtual commit' - assert(isSha(tree)) - else: - self.virtual = False - self.sha = sha.rstrip() - assert(isSha(self.sha)) - - def tree(self): - self.getInfo() - assert(self._tree != None) - return self._tree - - def shortInfo(self): - self.getInfo() - return str(self.sha) + ' ' + self.firstLineMsg - - def __str__(self): - return self.shortInfo() - - def getInfo(self): - if self.virtual or self.firstLineMsg != None: - return - else: - info = runProgram(['git-cat-file', 'commit', self.sha]) - info = info.split('\n') - msg = False - for l in info: - if msg: - self.firstLineMsg = l - break - else: - if l.startswith('tree'): - self._tree = l[5:].rstrip() - elif l == '': - msg = True - -class Graph: - def __init__(self): - self.commits = [] - self.shaMap = {} - - def addNode(self, node): - assert(isinstance(node, Commit)) - self.shaMap[node.sha] = node - self.commits.append(node) - for p in node.parents: - p.children.append(node) - return node - - def reachableNodes(self, n1, n2): - res = {} - def traverse(n): - res[n] = True - for p in n.parents: - traverse(p) - - traverse(n1) - traverse(n2) - return res - - def fixParents(self, node): - for x in range(0, len(node.parents)): - node.parents[x] = self.shaMap[node.parents[x]] - -# addDebug('buildGraph') -def buildGraph(heads): - debug('buildGraph heads:', heads) - for h in heads: - assert(isSha(h)) - - g = Graph() - - out = runProgram(['git-rev-list', '--parents'] + heads) - for l in out.split('\n'): - if l == '': - continue - shas = l.split(' ') - - # This is a hack, we temporarily use the 'parents' attribute - # to contain a list of SHA1:s. They are later replaced by proper - # Commit objects. - c = Commit(shas[0], shas[1:]) - - g.commits.append(c) - g.shaMap[c.sha] = c - - for c in g.commits: - g.fixParents(c) - - for c in g.commits: - for p in c.parents: - p.children.append(c) - return g - -# Write the empty tree to the object database and return its SHA1 -def writeEmptyTree(): - tmpIndex = os.environ.get('GIT_DIR', '.git') + '/merge-tmp-index' - def delTmpIndex(): - try: - os.unlink(tmpIndex) - except OSError: - pass - delTmpIndex() - newEnv = os.environ.copy() - newEnv['GIT_INDEX_FILE'] = tmpIndex - res = runProgram(['git-write-tree'], env=newEnv).rstrip() - delTmpIndex() - return res - -def addCommonRoot(graph): - roots = [] - for c in graph.commits: - if len(c.parents) == 0: - roots.append(c) - - superRoot = Commit(sha=None, parents=[], tree=writeEmptyTree()) - graph.addNode(superRoot) - for r in roots: - r.parents = [superRoot] - superRoot.children = roots - return superRoot - -def getCommonAncestors(graph, commit1, commit2): - '''Find the common ancestors for commit1 and commit2''' - assert(isinstance(commit1, Commit) and isinstance(commit2, Commit)) - - def traverse(start, set): - stack = [start] - while len(stack) > 0: - el = stack.pop() - set.add(el) - for p in el.parents: - if p not in set: - stack.append(p) - h1Set = Set() - h2Set = Set() - traverse(commit1, h1Set) - traverse(commit2, h2Set) - shared = h1Set.intersection(h2Set) - - if len(shared) == 0: - shared = [addCommonRoot(graph)] - - res = Set() - - for s in shared: - if len([c for c in s.children if c in shared]) == 0: - res.add(s) - return list(res) @@ -554,7 +554,7 @@ proc makewindow {} { pack .ctop.top.lbar.vlabel -side left -fill y global viewhlmenu selectedhlview set viewhlmenu [tk_optionMenu .ctop.top.lbar.vhl selectedhlview None] - $viewhlmenu entryconf 0 -command delvhighlight + $viewhlmenu entryconf None -command delvhighlight $viewhlmenu conf -font $uifont .ctop.top.lbar.vhl conf -font $uifont pack .ctop.top.lbar.vhl -side left -fill y @@ -1474,7 +1474,7 @@ proc doviewmenu {m first cmd op argv} { proc allviewmenus {n op args} { global viewhlmenu - doviewmenu .bar.view 7 [list showview $n] $op $args + doviewmenu .bar.view 5 [list showview $n] $op $args doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args } @@ -1516,7 +1516,7 @@ proc newviewok {top n} { set viewperm($n) $newviewperm($n) if {$newviewname($n) ne $viewname($n)} { set viewname($n) $newviewname($n) - doviewmenu .bar.view 7 [list showview $n] \ + doviewmenu .bar.view 5 [list showview $n] \ entryconf [list -label $viewname($n)] doviewmenu $viewhlmenu 1 [list addvhighlight $n] \ entryconf [list -label $viewname($n) -value $viewname($n)] @@ -1632,8 +1632,8 @@ proc showview {n} { set curview $n set selectedview $n - .bar.view entryconf 2 -state [expr {$n == 0? "disabled": "normal"}] - .bar.view entryconf 3 -state [expr {$n == 0? "disabled": "normal"}] + .bar.view entryconf Edit* -state [expr {$n == 0? "disabled": "normal"}] + .bar.view entryconf Delete* -state [expr {$n == 0? "disabled": "normal"}] if {![info exists viewdata($n)]} { set pending_select $selid @@ -4899,9 +4899,9 @@ proc rowmenu {x y id} { } else { set state normal } - $rowctxmenu entryconfigure 0 -state $state - $rowctxmenu entryconfigure 1 -state $state - $rowctxmenu entryconfigure 2 -state $state + $rowctxmenu entryconfigure "Diff this*" -state $state + $rowctxmenu entryconfigure "Diff selected*" -state $state + $rowctxmenu entryconfigure "Make patch" -state $state set rowmenuid $id tk_popup $rowctxmenu $x $y } @@ -6305,8 +6305,8 @@ if {$cmdline_files ne {} || $revtreeargs ne {}} { set viewargs(1) $revtreeargs set viewperm(1) 0 addviewmenu 1 - .bar.view entryconf 2 -state normal - .bar.view entryconf 3 -state normal + .bar.view entryconf Edit* -state normal + .bar.view entryconf Delete* -state normal } if {[info exists permviews]} { diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 6ae7e80351..65fcdb0f28 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -120,7 +120,7 @@ our %feature = ( # To disable system wide have in $GITWEB_CONFIG # $feature{'snapshot'}{'default'} = [undef]; # To have project specific config enable override in $GITWEB_CONFIG - # $feature{'blame'}{'override'} = 1; + # $feature{'snapshot'}{'override'} = 1; # and in project config gitweb.snapshot = none|gzip|bzip2; 'snapshot' => { 'sub' => \&feature_snapshot, @@ -128,6 +128,12 @@ our %feature = ( # => [content-encoding, suffix, program] 'default' => ['x-gzip', 'gz', 'gzip']}, + # Enable text search, which will list the commits which match author, + # committer or commit text to a given string. Enabled by default. + 'search' => { + 'override' => 0, + 'default' => [1]}, + # Enable the pickaxe search, which will list the commits that modified # a given string in a file. This can be practical and quite faster # alternative to 'blame', but still potentially CPU-intensive. @@ -351,6 +357,9 @@ if (defined $searchtext) { if ($searchtext =~ m/[^a-zA-Z0-9_\.\/\-\+\:\@ ]/) { die_error(undef, "Invalid search parameter"); } + if (length($searchtext) < 2) { + die_error(undef, "At least two characters are required for search parameter"); + } $searchtext = quotemeta $searchtext; } @@ -434,6 +443,7 @@ my %actions = ( "tags" => \&git_tags, "tree" => \&git_tree, "snapshot" => \&git_snapshot, + "object" => \&git_object, # those below don't need $project "opml" => \&git_opml, "project_list" => \&git_project_list, @@ -585,7 +595,21 @@ sub esc_html ($;%) { return $str; } -# Make control characterss "printable". +# quote control characters and escape filename to HTML +sub esc_path { + my $str = shift; + my %opts = @_; + + $str = to_utf8($str); + $str = escapeHTML($str); + if ($opts{'-nbsp'}) { + $str =~ s/ / /g; + } + $str =~ s|([[:cntrl:]])|quot_cec($1)|eg; + return $str; +} + +# Make control characters "printable", using character escape codes (CEC) sub quot_cec { my $cntrl = shift; my %es = ( # character escape codes, aka escape sequences @@ -605,22 +629,14 @@ sub quot_cec { return "<span class=\"cntrl\">$chr</span>"; } -# Alternatively use unicode control pictures codepoints. +# Alternatively use unicode control pictures codepoints, +# Unicode "printable representation" (PR) sub quot_upr { my $cntrl = shift; my $chr = sprintf('&#%04d;', 0x2400+ord($cntrl)); return "<span class=\"cntrl\">$chr</span>"; } -# quote control characters and escape filename to HTML -sub esc_path { - my $str = shift; - - $str = esc_html($str); - $str =~ s|([[:cntrl:]])|quot_cec($1)|eg; - return $str; -} - # git may return quoted and escaped filenames sub unquote { my $str = shift; @@ -821,14 +837,12 @@ sub format_log_line_html { my $line = shift; $line = esc_html($line, -nbsp=>1); - if ($line =~ m/([0-9a-fA-F]{40})/) { + if ($line =~ m/([0-9a-fA-F]{8,40})/) { my $hash_text = $1; - if (git_get_type($hash_text) eq "commit") { - my $link = - $cgi->a({-href => href(action=>"commit", hash=>$hash_text), - -class => "text"}, $hash_text); - $line =~ s/$hash_text/$link/; - } + my $link = + $cgi->a({-href => href(action=>"object", hash=>$hash_text), + -class => "text"}, $hash_text); + $line =~ s/$hash_text/$link/; } return $line; } @@ -850,7 +864,8 @@ sub format_ref_marker { $name = $ref; } - $markers .= " <span class=\"$type\">" . esc_html($name) . "</span>"; + $markers .= " <span class=\"$type\" title=\"$ref\">" . + esc_html($name) . "</span>"; } } @@ -1133,8 +1148,9 @@ sub git_get_last_activity { $git_dir = "$projectroot/$path"; open($fd, "-|", git_cmd(), 'for-each-ref', - '--format=%(refname) %(committer)', + '--format=%(committer)', '--sort=-committerdate', + '--count=1', 'refs/heads') or return; my $most_recent = <$fd>; close $fd or return; @@ -1148,14 +1164,15 @@ sub git_get_last_activity { sub git_get_references { my $type = shift || ""; my %refs; - # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11 - # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{} - open my $fd, "-|", $GIT, "peek-remote", "$projectroot/$project/" + # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11 + # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{} + open my $fd, "-|", git_cmd(), "show-ref", "--dereference", + ($type ? ("--", "refs/$type") : ()) # use -- <pattern> if $type or return; while (my $line = <$fd>) { chomp $line; - if ($line =~ m/^([0-9a-fA-F]{40})\trefs\/($type\/?[^\^]+)/) { + if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type/?[^^]+)!) { if (defined $refs{$1}) { push @{$refs{$1}}, $2; } else { @@ -1253,42 +1270,32 @@ sub parse_tag { return %tag } -sub parse_commit { - my $commit_id = shift; - my $commit_text = shift; - - my @commit_lines; +sub parse_commit_text { + my ($commit_text) = @_; + my @commit_lines = split '\n', $commit_text; my %co; - if (defined $commit_text) { - @commit_lines = @$commit_text; - } else { - local $/ = "\0"; - open my $fd, "-|", git_cmd(), "rev-list", - "--header", "--parents", "--max-count=1", - $commit_id, "--" - or return; - @commit_lines = split '\n', <$fd>; - close $fd or return; - pop @commit_lines; - } + pop @commit_lines; # Remove '\0' + my $header = shift @commit_lines; if (!($header =~ m/^[0-9a-fA-F]{40}/)) { return; } - ($co{'id'}, my @parents) = split ' ', $header; - $co{'parents'} = \@parents; - $co{'parent'} = $parents[0]; + $co{'id'} = $header; + my @parents; while (my $line = shift @commit_lines) { last if $line eq "\n"; if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) { $co{'tree'} = $1; + } elsif ($line =~ m/^parent ([0-9a-fA-F]{40})$/) { + push @parents, $1; } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) { $co{'author'} = $1; $co{'author_epoch'} = $2; $co{'author_tz'} = $3; - if ($co{'author'} =~ m/^([^<]+) </) { - $co{'author_name'} = $1; + if ($co{'author'} =~ m/^([^<]+) <([^>]*)>/) { + $co{'author_name'} = $1; + $co{'author_email'} = $2; } else { $co{'author_name'} = $co{'author'}; } @@ -1297,12 +1304,19 @@ sub parse_commit { $co{'committer_epoch'} = $2; $co{'committer_tz'} = $3; $co{'committer_name'} = $co{'committer'}; - $co{'committer_name'} =~ s/ <.*//; + if ($co{'committer'} =~ m/^([^<]+) <([^>]*)>/) { + $co{'committer_name'} = $1; + $co{'committer_email'} = $2; + } else { + $co{'committer_name'} = $co{'committer'}; + } } } if (!defined $co{'tree'}) { return; }; + $co{'parents'} = \@parents; + $co{'parent'} = $parents[0]; foreach my $title (@commit_lines) { $title =~ s/^ //; @@ -1352,6 +1366,51 @@ sub parse_commit { return %co; } +sub parse_commit { + my ($commit_id) = @_; + my %co; + + local $/ = "\0"; + + open my $fd, "-|", git_cmd(), "rev-list", + "--header", + "--max-count=1", + $commit_id, + "--", + or die_error(undef, "Open git-rev-list failed"); + %co = parse_commit_text(<$fd>); + close $fd; + + return %co; +} + +sub parse_commits { + my ($commit_id, $maxcount, $skip, $arg, $filename) = @_; + my @cos; + + $maxcount ||= 1; + $skip ||= 0; + + local $/ = "\0"; + + open my $fd, "-|", git_cmd(), "rev-list", + "--header", + ($arg ? ($arg) : ()), + ("--max-count=" . $maxcount), + ("--skip=" . $skip), + $commit_id, + "--", + ($filename ? ($filename) : ()) + or die_error(undef, "Open git-rev-list failed"); + while (my $line = <$fd>) { + my %co = parse_commit_text($line); + push @cos, \%co; + } + close $fd; + + return wantarray ? @cos : \@cos; +} + # parse ref from ref_file, given by ref_id, with given type sub parse_ref { my $ref_file = shift; @@ -1713,6 +1772,9 @@ EOF print " / $action"; } print "\n"; + } + my ($have_search) = gitweb_check_feature('search'); + if ((defined $project) && ($have_search)) { if (!defined $searchtext) { $searchtext = ""; } @@ -1976,12 +2038,73 @@ sub git_print_log ($;%) { } } +# return link target (what link points to) +sub git_get_link_target { + my $hash = shift; + my $link_target; + + # read link + open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash + or return; + { + local $/; + $link_target = <$fd>; + } + close $fd + or return; + + return $link_target; +} + +# given link target, and the directory (basedir) the link is in, +# return target of link relative to top directory (top tree); +# return undef if it is not possible (including absolute links). +sub normalize_link_target { + my ($link_target, $basedir, $hash_base) = @_; + + # we can normalize symlink target only if $hash_base is provided + return unless $hash_base; + + # absolute symlinks (beginning with '/') cannot be normalized + return if (substr($link_target, 0, 1) eq '/'); + + # normalize link target to path from top (root) tree (dir) + my $path; + if ($basedir) { + $path = $basedir . '/' . $link_target; + } else { + # we are in top (root) tree (dir) + $path = $link_target; + } + + # remove //, /./, and /../ + my @path_parts; + foreach my $part (split('/', $path)) { + # discard '.' and '' + next if (!$part || $part eq '.'); + # handle '..' + if ($part eq '..') { + if (@path_parts) { + pop @path_parts; + } else { + # link leads outside repository (outside top dir) + return; + } + } else { + push @path_parts, $part; + } + } + $path = join('/', @path_parts); + + return $path; +} + # print tree entry (row of git_tree), but without encompassing <tr> element sub git_print_tree_entry { my ($t, $basedir, $hash_base, $have_blame) = @_; my %base_key = (); - $base_key{hash_base} = $hash_base if defined $hash_base; + $base_key{'hash_base'} = $hash_base if defined $hash_base; # The format of a table row is: mode list link. Where mode is # the mode of the entry, list is the name of the entry, an href, @@ -1992,16 +2115,31 @@ sub git_print_tree_entry { print "<td class=\"list\">" . $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}", %base_key), - -class => "list"}, esc_path($t->{'name'})) . "</td>\n"; + -class => "list"}, esc_path($t->{'name'})); + if (S_ISLNK(oct $t->{'mode'})) { + my $link_target = git_get_link_target($t->{'hash'}); + if ($link_target) { + my $norm_target = normalize_link_target($link_target, $basedir, $hash_base); + if (defined $norm_target) { + print " -> " . + $cgi->a({-href => href(action=>"object", hash_base=>$hash_base, + file_name=>$norm_target), + -title => $norm_target}, esc_path($link_target)); + } else { + print " -> " . esc_path($link_target); + } + } + } + print "</td>\n"; print "<td class=\"link\">"; print $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key)}, - "blob"); + file_name=>"$basedir$t->{'name'}", %base_key)}, + "blob"); if ($have_blame) { print " | " . $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key)}, - "blame"); + file_name=>"$basedir$t->{'name'}", %base_key)}, + "blame"); } if (defined $hash_base) { print " | " . @@ -2023,8 +2161,8 @@ sub git_print_tree_entry { print "</td>\n"; print "<td class=\"link\">"; print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key)}, - "tree"); + file_name=>"$basedir$t->{'name'}", %base_key)}, + "tree"); if (defined $hash_base) { print " | " . $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, @@ -2428,6 +2566,7 @@ sub git_project_list_body { ($pr->{'age'}, $pr->{'age_string'}) = @aa; if (!defined $pr->{'descr'}) { my $descr = git_get_project_description($pr->{'path'}) || ""; + $pr->{'descr_long'} = to_utf8($descr); $pr->{'descr'} = chop_str($descr, 25, 5); } if (!defined $pr->{'owner'}) { @@ -2463,7 +2602,7 @@ sub git_project_list_body { } else { print "<th>" . $cgi->a({-href => href(project=>undef, order=>'project'), - -class => "header"}, "Project") . + -class => "header"}, "Project") . "</th>\n"; } if ($order eq "descr") { @@ -2472,7 +2611,7 @@ sub git_project_list_body { } else { print "<th>" . $cgi->a({-href => href(project=>undef, order=>'descr'), - -class => "header"}, "Description") . + -class => "header"}, "Description") . "</th>\n"; } if ($order eq "owner") { @@ -2481,7 +2620,7 @@ sub git_project_list_body { } else { print "<th>" . $cgi->a({-href => href(project=>undef, order=>'owner'), - -class => "header"}, "Owner") . + -class => "header"}, "Owner") . "</th>\n"; } if ($order eq "age") { @@ -2490,7 +2629,7 @@ sub git_project_list_body { } else { print "<th>" . $cgi->a({-href => href(project=>undef, order=>'age'), - -class => "header"}, "Last Change") . + -class => "header"}, "Last Change") . "</th>\n"; } print "<th></th>\n" . @@ -2515,7 +2654,9 @@ sub git_project_list_body { } print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"), -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" . - "<td>" . esc_html($pr->{'descr'}) . "</td>\n" . + "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"), + -class => "list", -title => $pr->{'descr_long'}}, + esc_html($pr->{'descr'})) . "</td>\n" . "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n"; print "<td class=\"". age_class($pr->{'age'}) . "\">" . $pr->{'age_string'} . "</td>\n" . @@ -2541,18 +2682,19 @@ sub git_project_list_body { sub git_shortlog_body { # uses global variable $project - my ($revlist, $from, $to, $refs, $extra) = @_; + my ($commitlist, $from, $to, $refs, $extra) = @_; + + my $have_snapshot = gitweb_have_snapshot(); $from = 0 unless defined $from; - $to = $#{$revlist} if (!defined $to || $#{$revlist} < $to); + $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to); print "<table class=\"shortlog\" cellspacing=\"0\">\n"; my $alternate = 1; for (my $i = $from; $i <= $to; $i++) { - my $commit = $revlist->[$i]; - #my $ref = defined $refs ? format_ref_marker($refs, $commit) : ''; + my %co = %{$commitlist->[$i]}; + my $commit = $co{'id'}; my $ref = format_ref_marker($refs, $commit); - my %co = parse_commit($commit); if ($alternate) { print "<tr class=\"dark\">\n"; } else { @@ -2570,7 +2712,7 @@ sub git_shortlog_body { $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " . $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " . $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree"); - if (gitweb_have_snapshot()) { + if ($have_snapshot) { print " | " . $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot"); } print "</td>\n" . @@ -2586,23 +2728,19 @@ sub git_shortlog_body { sub git_history_body { # Warning: assumes constant type (blob or tree) during history - my ($revlist, $from, $to, $refs, $hash_base, $ftype, $extra) = @_; + my ($commitlist, $from, $to, $refs, $hash_base, $ftype, $extra) = @_; $from = 0 unless defined $from; - $to = $#{$revlist} unless (defined $to && $to <= $#{$revlist}); + $to = $#{$commitlist} unless (defined $to && $to <= $#{$commitlist}); print "<table class=\"history\" cellspacing=\"0\">\n"; my $alternate = 1; for (my $i = $from; $i <= $to; $i++) { - if ($revlist->[$i] !~ m/^([0-9a-fA-F]{40})/) { - next; - } - - my $commit = $1; - my %co = parse_commit($commit); + my %co = %{$commitlist->[$i]}; if (!%co) { next; } + my $commit = $co{'id'}; my $ref = format_ref_marker($refs, $commit); @@ -2745,6 +2883,58 @@ sub git_heads_body { print "</table>\n"; } +sub git_search_grep_body { + my ($commitlist, $from, $to, $extra) = @_; + $from = 0 unless defined $from; + $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to); + + print "<table class=\"grep\" cellspacing=\"0\">\n"; + my $alternate = 1; + for (my $i = $from; $i <= $to; $i++) { + my %co = %{$commitlist->[$i]}; + if (!%co) { + next; + } + my $commit = $co{'id'}; + if ($alternate) { + print "<tr class=\"dark\">\n"; + } else { + print "<tr class=\"light\">\n"; + } + $alternate ^= 1; + print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . + "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" . + "<td>" . + $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"}, + esc_html(chop_str($co{'title'}, 50)) . "<br/>"); + my $comment = $co{'comment'}; + foreach my $line (@$comment) { + if ($line =~ m/^(.*)($searchtext)(.*)$/i) { + my $lead = esc_html($1) || ""; + $lead = chop_str($lead, 30, 10); + my $match = esc_html($2) || ""; + my $trail = esc_html($3) || ""; + $trail = chop_str($trail, 30, 10); + my $text = "$lead<span class=\"match\">$match</span>$trail"; + print chop_str($text, 80, 5) . "<br/>\n"; + } + } + print "</td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") . + " | " . + $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree"); + print "</td>\n" . + "</tr>\n"; + } + if (defined $extra) { + print "<tr>\n" . + "<td colspan=\"3\">$extra</td>\n" . + "</tr>\n"; + } + print "</table>\n"; +} + ## ====================================================================== ## ====================================================================== ## actions @@ -2816,15 +3006,17 @@ sub git_project_index { sub git_summary { my $descr = git_get_project_description($project) || "none"; - my $head = git_get_head_hash($project); - my %co = parse_commit($head); + my %co = parse_commit("HEAD"); my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'}); + my $head = $co{'id'}; my $owner = git_get_project_owner($project); my $refs = git_get_references(); - my @taglist = git_get_tags_list(15); - my @headlist = git_get_heads_list(15); + # These get_*_list functions return one more to allow us to see if + # there are more ... + my @taglist = git_get_tags_list(16); + my @headlist = git_get_heads_list(16); my @forklist; my ($check_forks) = gitweb_check_feature('forks'); @@ -2860,30 +3052,32 @@ sub git_summary { } } - open my $fd, "-|", git_cmd(), "rev-list", "--max-count=17", - git_get_head_hash($project), "--" - or die_error(undef, "Open git-rev-list failed"); - my @revlist = map { chomp; $_ } <$fd>; - close $fd; + # we need to request one more than 16 (0..15) to check if + # those 16 are all + my @commitlist = parse_commits($head, 17); git_print_header_div('shortlog'); - git_shortlog_body(\@revlist, 0, 15, $refs, + git_shortlog_body(\@commitlist, 0, 15, $refs, + $#commitlist <= 15 ? undef : $cgi->a({-href => href(action=>"shortlog")}, "...")); if (@taglist) { git_print_header_div('tags'); git_tags_body(\@taglist, 0, 15, + $#taglist <= 15 ? undef : $cgi->a({-href => href(action=>"tags")}, "...")); } if (@headlist) { git_print_header_div('heads'); git_heads_body(\@headlist, $head, 0, 15, + $#headlist <= 15 ? undef : $cgi->a({-href => href(action=>"heads")}, "...")); } if (@forklist) { git_print_header_div('forks'); git_project_list_body(\@forklist, undef, 0, 15, + $#forklist <= 15 ? undef : $cgi->a({-href => href(action=>"forks")}, "..."), 'noheader'); } @@ -3213,10 +3407,13 @@ sub git_blob { open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash or die_error(undef, "Couldn't cat $file_name, $hash"); my $mimetype = blob_mimetype($fd, $file_name); - if ($mimetype !~ m/^text\//) { + if ($mimetype !~ m!^(?:text/|image/(?:gif|png|jpeg)$)!) { close $fd; return git_blob_plain($mimetype); } + # we can have blame only for text/* mimetype + $have_blame &&= ($mimetype =~ m!^text/!); + git_header_html(undef, $expires); my $formats_nav = ''; if (defined $hash_base && (my %co = parse_commit($hash_base))) { @@ -3253,13 +3450,24 @@ sub git_blob { } git_print_page_path($file_name, "blob", $hash_base); print "<div class=\"page_body\">\n"; - my $nr; - while (my $line = <$fd>) { - chomp $line; - $nr++; - $line = untabify($line); - printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n", - $nr, $nr, $nr, esc_html($line, -nbsp=>1); + if ($mimetype =~ m!^text/!) { + my $nr; + while (my $line = <$fd>) { + chomp $line; + $nr++; + $line = untabify($line); + printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n", + $nr, $nr, $nr, esc_html($line, -nbsp=>1); + } + } elsif ($mimetype =~ m!^image/!) { + print qq!<img type="$mimetype"!; + if ($file_name) { + print qq! alt="$file_name" title="$file_name"!; + } + print qq! src="! . + href(action=>"blob_plain", hash=>$hash, + hash_base=>$hash_base, file_name=>$file_name) . + qq!" />\n!; } close $fd or print "Reading blob failed.\n"; @@ -3384,8 +3592,7 @@ sub git_snapshot { my $filename = basename($project) . "-$hash.tar.$suffix"; print $cgi->header( - -type => 'application/x-tar', - -content_encoding => $ctype, + -type => "application/$ctype", -content_disposition => 'inline; filename="' . "$filename" . '"', -status => '200 OK'); @@ -3412,28 +3619,25 @@ sub git_log { } my $refs = git_get_references(); - my $limit = sprintf("--max-count=%i", (100 * ($page+1))); - open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash, "--" - or die_error(undef, "Open git-rev-list failed"); - my @revlist = map { chomp; $_ } <$fd>; - close $fd; + my @commitlist = parse_commits($hash, 101, (100 * $page)); - my $paging_nav = format_paging_nav('log', $hash, $head, $page, $#revlist); + my $paging_nav = format_paging_nav('log', $hash, $head, $page, (100 * ($page+1))); git_header_html(); git_print_page_nav('log','', $hash,undef,undef, $paging_nav); - if (!@revlist) { + if (!@commitlist) { my %co = parse_commit($hash); git_print_header_div('summary', $project); print "<div class=\"page_body\"> Last change $co{'age_string'}.<br/><br/></div>\n"; } - for (my $i = ($page * 100); $i <= $#revlist; $i++) { - my $commit = $revlist[$i]; - my $ref = format_ref_marker($refs, $commit); - my %co = parse_commit($commit); + my $to = ($#commitlist >= 99) ? (99) : ($#commitlist); + for (my $i = 0; $i <= $to; $i++) { + my %co = %{$commitlist[$i]}; next if !%co; + my $commit = $co{'id'}; + my $ref = format_ref_marker($refs, $commit); my %ad = parse_date($co{'author_epoch'}); git_print_header_div('commit', "<span class=\"age\">$co{'age_string'}</span>" . @@ -3455,6 +3659,12 @@ sub git_log { git_print_log($co{'comment'}, -final_empty_line=> 1); print "</div>\n"; } + if ($#commitlist >= 100) { + print "<div class=\"page_nav\">\n"; + print $cgi->a({-href => href(action=>"log", hash=>$hash, page=>$page+1), + -accesskey => "n", -title => "Alt-n"}, "next"); + print "</div>\n"; + } git_footer_html(); } @@ -3467,15 +3677,46 @@ sub git_commit { my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'}); my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'}); - my $parent = $co{'parent'}; + my $parent = $co{'parent'}; + my $parents = $co{'parents'}; # listref + + # we need to prepare $formats_nav before any parameter munging + my $formats_nav; + if (!defined $parent) { + # --root commitdiff + $formats_nav .= '(initial)'; + } elsif (@$parents == 1) { + # single parent commit + $formats_nav .= + '(parent: ' . + $cgi->a({-href => href(action=>"commit", + hash=>$parent)}, + esc_html(substr($parent, 0, 7))) . + ')'; + } else { + # merge commit + $formats_nav .= + '(merge: ' . + join(' ', map { + $cgi->a({-href => href(action=>"commitdiff", + hash=>$_)}, + esc_html(substr($_, 0, 7))); + } @$parents ) . + ')'; + } + if (!defined $parent) { $parent = "--root"; } - open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id", - @diff_opts, $parent, $hash, "--" - or die_error(undef, "Open git-diff-tree failed"); - my @difftree = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading git-diff-tree failed"); + my @difftree; + if (@$parents <= 1) { + # difftree output is not printed for merges + open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id", + @diff_opts, $parent, $hash, "--" + or die_error(undef, "Open git-diff-tree failed"); + @difftree = map { chomp; $_ } <$fd>; + close $fd or die_error(undef, "Reading git-diff-tree failed"); + } # non-textual hash id's can be cached my $expires; @@ -3487,16 +3728,10 @@ sub git_commit { my $have_snapshot = gitweb_have_snapshot(); - my @views_nav = (); - if (defined $file_name && defined $co{'parent'}) { - push @views_nav, - $cgi->a({-href => href(action=>"blame", hash_parent=>$parent, file_name=>$file_name)}, - "blame"); - } git_header_html(undef, $expires); git_print_page_nav('commit', '', $hash, $co{'tree'}, $hash, - join (' | ', @views_nav)); + $formats_nav); if (defined $co{'parent'}) { git_print_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash); @@ -3537,7 +3772,7 @@ sub git_commit { } print "</td>" . "</tr>\n"; - my $parents = $co{'parents'}; + foreach my $par (@$parents) { print "<tr>" . "<td>parent</td>" . @@ -3559,11 +3794,61 @@ sub git_commit { git_print_log($co{'comment'}); print "</div>\n"; - git_difftree_body(\@difftree, $hash, $parent); + if (@$parents <= 1) { + # do not output difftree/whatchanged for merges + git_difftree_body(\@difftree, $hash, $parent); + } git_footer_html(); } +sub git_object { + # object is defined by: + # - hash or hash_base alone + # - hash_base and file_name + my $type; + + # - hash or hash_base alone + if ($hash || ($hash_base && !defined $file_name)) { + my $object_id = $hash || $hash_base; + + my $git_command = git_cmd_str(); + open my $fd, "-|", "$git_command cat-file -t $object_id 2>/dev/null" + or die_error('404 Not Found', "Object does not exist"); + $type = <$fd>; + chomp $type; + close $fd + or die_error('404 Not Found', "Object does not exist"); + + # - hash_base and file_name + } elsif ($hash_base && defined $file_name) { + $file_name =~ s,/+$,,; + + system(git_cmd(), "cat-file", '-e', $hash_base) == 0 + or die_error('404 Not Found', "Base object does not exist"); + + # here errors should not hapen + open my $fd, "-|", git_cmd(), "ls-tree", $hash_base, "--", $file_name + or die_error(undef, "Open git-ls-tree failed"); + my $line = <$fd>; + close $fd; + + #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' + unless ($line && $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/) { + die_error('404 Not Found', "File or directory for given base does not exist"); + } + $type = $2; + $hash = $3; + } else { + die_error('404 Not Found', "Not enough information to find object"); + } + + print $cgi->redirect(-uri => href(action=>$type, -full=>1, + hash=>$hash, hash_base=>$hash_base, + file_name=>$file_name), + -status => '302 Found'); +} + sub git_blobdiff { my $format = shift || 'html'; @@ -3908,12 +4193,7 @@ sub git_history { $ftype = git_get_type($hash); } - open my $fd, "-|", - git_cmd(), "rev-list", $limit, "--full-history", $hash_base, "--", $file_name - or die_error(undef, "Open git-rev-list-failed"); - my @revlist = map { chomp; $_ } <$fd>; - close $fd - or die_error(undef, "Reading git-rev-list failed"); + my @commitlist = parse_commits($hash_base, 101, (100 * $page), "--full-history", $file_name); my $paging_nav = ''; if ($page > 0) { @@ -3929,7 +4209,7 @@ sub git_history { $paging_nav .= "first"; $paging_nav .= " ⋅ prev"; } - if ($#revlist >= (100 * ($page+1)-1)) { + if ($#commitlist >= 100) { $paging_nav .= " ⋅ " . $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name, page=>$page+1), @@ -3938,11 +4218,11 @@ sub git_history { $paging_nav .= " ⋅ next"; } my $next_link = ''; - if ($#revlist >= (100 * ($page+1)-1)) { + if ($#commitlist >= 100) { $next_link = $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name, page=>$page+1), - -title => "Alt-n"}, "next"); + -accesskey => "n", -title => "Alt-n"}, "next"); } git_header_html(); @@ -3950,13 +4230,17 @@ sub git_history { git_print_header_div('commit', esc_html($co{'title'}), $hash_base); git_print_page_path($file_name, $ftype, $hash_base); - git_history_body(\@revlist, ($page * 100), $#revlist, + git_history_body(\@commitlist, 0, 99, $refs, $hash_base, $ftype, $next_link); git_footer_html(); } sub git_search { + my ($have_search) = gitweb_check_feature('search'); + if (!$have_search) { + die_error('403 Permission denied', "Permission denied"); + } if (!defined $searchtext) { die_error(undef, "Text field empty"); } @@ -3967,6 +4251,9 @@ sub git_search { if (!%co) { die_error(undef, "Unknown commit object"); } + if (!defined $page) { + $page = 0; + } $searchtype ||= 'commit'; if ($searchtype eq 'pickaxe') { @@ -3979,66 +4266,63 @@ sub git_search { } git_header_html(); - git_print_page_nav('','', $hash,$co{'tree'},$hash); - git_print_header_div('commit', esc_html($co{'title'}), $hash); - print "<table cellspacing=\"0\">\n"; - my $alternate = 1; if ($searchtype eq 'commit' or $searchtype eq 'author' or $searchtype eq 'committer') { - $/ = "\0"; - open my $fd, "-|", git_cmd(), "rev-list", - "--header", "--parents", $hash, "--" - or next; - while (my $commit_text = <$fd>) { - if (!grep m/$searchtext/i, $commit_text) { - next; - } - if ($searchtype eq 'author' && !grep m/\nauthor .*$searchtext/i, $commit_text) { - next; - } - if ($searchtype eq 'committer' && !grep m/\ncommitter .*$searchtext/i, $commit_text) { - next; - } - my @commit_lines = split "\n", $commit_text; - my %co = parse_commit(undef, \@commit_lines); - if (!%co) { - next; - } - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - $alternate ^= 1; - print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . - "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" . - "<td>" . - $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"}, - esc_html(chop_str($co{'title'}, 50)) . "<br/>"); - my $comment = $co{'comment'}; - foreach my $line (@$comment) { - if ($line =~ m/^(.*)($searchtext)(.*)$/i) { - my $lead = esc_html($1) || ""; - $lead = chop_str($lead, 30, 10); - my $match = esc_html($2) || ""; - my $trail = esc_html($3) || ""; - $trail = chop_str($trail, 30, 10); - my $text = "$lead<span class=\"match\">$match</span>$trail"; - print chop_str($text, 80, 5) . "<br/>\n"; - } - } - print "</td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") . - " | " . - $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree"); - print "</td>\n" . - "</tr>\n"; + my $greptype; + if ($searchtype eq 'commit') { + $greptype = "--grep="; + } elsif ($searchtype eq 'author') { + $greptype = "--author="; + } elsif ($searchtype eq 'committer') { + $greptype = "--committer="; } - close $fd; + $greptype .= $searchtext; + my @commitlist = parse_commits($hash, 101, (100 * $page), $greptype); + + my $paging_nav = ''; + if ($page > 0) { + $paging_nav .= + $cgi->a({-href => href(action=>"search", hash=>$hash, + searchtext=>$searchtext, searchtype=>$searchtype)}, + "first"); + $paging_nav .= " ⋅ " . + $cgi->a({-href => href(action=>"search", hash=>$hash, + searchtext=>$searchtext, searchtype=>$searchtype, + page=>$page-1), + -accesskey => "p", -title => "Alt-p"}, "prev"); + } else { + $paging_nav .= "first"; + $paging_nav .= " ⋅ prev"; + } + if ($#commitlist >= 100) { + $paging_nav .= " ⋅ " . + $cgi->a({-href => href(action=>"search", hash=>$hash, + searchtext=>$searchtext, searchtype=>$searchtype, + page=>$page+1), + -accesskey => "n", -title => "Alt-n"}, "next"); + } else { + $paging_nav .= " ⋅ next"; + } + my $next_link = ''; + if ($#commitlist >= 100) { + $next_link = + $cgi->a({-href => href(action=>"search", hash=>$hash, + searchtext=>$searchtext, searchtype=>$searchtype, + page=>$page+1), + -accesskey => "n", -title => "Alt-n"}, "next"); + } + + git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav); + git_print_header_div('commit', esc_html($co{'title'}), $hash); + git_search_grep_body(\@commitlist, 0, 99, $next_link); } if ($searchtype eq 'pickaxe') { + git_print_page_nav('','', $hash,$co{'tree'},$hash); + git_print_header_div('commit', esc_html($co{'title'}), $hash); + + print "<table cellspacing=\"0\">\n"; + my $alternate = 1; $/ = "\n"; my $git_command = git_cmd_str(); open my $fd, "-|", "$git_command rev-list $hash | " . @@ -4093,8 +4377,9 @@ sub git_search { } } close $fd; + + print "</table>\n"; } - print "</table>\n"; git_footer_html(); } @@ -4133,26 +4418,21 @@ sub git_shortlog { } my $refs = git_get_references(); - my $limit = sprintf("--max-count=%i", (100 * ($page+1))); - open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash, "--" - or die_error(undef, "Open git-rev-list failed"); - my @revlist = map { chomp; $_ } <$fd>; - close $fd; + my @commitlist = parse_commits($head, 101, (100 * $page)); - my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, $#revlist); + my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, (100 * ($page+1))); my $next_link = ''; - if ($#revlist >= (100 * ($page+1)-1)) { + if ($#commitlist >= 100) { $next_link = $cgi->a({-href => href(action=>"shortlog", hash=>$hash, page=>$page+1), - -title => "Alt-n"}, "next"); + -accesskey => "n", -title => "Alt-n"}, "next"); } - git_header_html(); git_print_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav); git_print_header_div('summary', $project); - git_shortlog_body(\@revlist, ($page * 100), $#revlist, $refs, $next_link); + git_shortlog_body(\@commitlist, 0, 99, $refs, $next_link); git_footer_html(); } @@ -4172,11 +4452,7 @@ sub git_feed { # log/feed of current (HEAD) branch, log of given branch, history of file/directory my $head = $hash || 'HEAD'; - open my $fd, "-|", git_cmd(), "rev-list", "--max-count=150", - $head, "--", (defined $file_name ? $file_name : ()) - or die_error(undef, "Open git-rev-list failed"); - my @revlist = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading git-rev-list failed"); + my @commitlist = parse_commits($head, 150); my %latest_commit; my %latest_date; @@ -4186,9 +4462,9 @@ sub git_feed { # browser (feed reader) prefers text/xml $content_type = 'text/xml'; } - if (defined($revlist[0])) { - %latest_commit = parse_commit($revlist[0]); - %latest_date = parse_date($latest_commit{'committer_epoch'}); + if (defined($commitlist[0])) { + %latest_commit = %{$commitlist[0]}; + %latest_date = parse_date($latest_commit{'author_epoch'}); print $cgi->header( -type => $content_type, -charset => 'utf-8', @@ -4266,7 +4542,7 @@ XML } if (defined $logo_url) { # not twice as wide as tall: 72 x 27 pixels - print "<logo>" . esc_url($logo_url) . "</logo>\n"; + print "<logo>" . esc_url($logo) . "</logo>\n"; } if (! %latest_date) { # dummy date to keep the feed valid until commits trickle in: @@ -4277,17 +4553,17 @@ XML } # contents - for (my $i = 0; $i <= $#revlist; $i++) { - my $commit = $revlist[$i]; - my %co = parse_commit($commit); + for (my $i = 0; $i <= $#commitlist; $i++) { + my %co = %{$commitlist[$i]}; + my $commit = $co{'id'}; # we read 150, we always show 30 and the ones more recent than 48 hours - if (($i >= 20) && ((time - $co{'committer_epoch'}) > 48*60*60)) { + if (($i >= 20) && ((time - $co{'author_epoch'}) > 48*60*60)) { last; } - my %cd = parse_date($co{'committer_epoch'}); + my %cd = parse_date($co{'author_epoch'}); # get list of changed files - open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, + open my $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, $co{'parent'}, $co{'id'}, "--", (defined $file_name ? $file_name : ()) or next; my @difftree = map { chomp; $_ } <$fd>; @@ -4310,9 +4586,19 @@ XML print "<entry>\n" . "<title type=\"html\">" . esc_html($co{'title'}) . "</title>\n" . "<updated>$cd{'iso-8601'}</updated>\n" . - "<author><name>" . esc_html($co{'author_name'}) . "</name></author>\n" . + "<author>\n" . + " <name>" . esc_html($co{'author_name'}) . "</name>\n"; + if ($co{'author_email'}) { + print " <email>" . esc_html($co{'author_email'}) . "</email>\n"; + } + print "</author>\n" . # use committer for contributor - "<contributor><name>" . esc_html($co{'committer_name'}) . "</name></contributor>\n" . + "<contributor>\n" . + " <name>" . esc_html($co{'committer_name'}) . "</name>\n"; + if ($co{'committer_email'}) { + print " <email>" . esc_html($co{'committer_email'}) . "</email>\n"; + } + print "</contributor>\n" . "<published>$cd{'iso-8601'}</published>\n" . "<link rel=\"alternate\" type=\"text/html\" href=\"$co_url\" />\n" . "<id>$co_url</id>\n" . @@ -1,5 +1,4 @@ #include "cache.h" -#include <regex.h> #include "grep.h" void append_grep_pattern(struct grep_opt *opt, const char *pat, @@ -3,12 +3,11 @@ * * Builtin help-related commands (help, usage, version) */ -#include <sys/ioctl.h> #include "cache.h" #include "builtin.h" #include "exec_cmd.h" #include "common-cmds.h" - +#include <sys/ioctl.h> /* most GUI terminals set COLUMNS (although some don't export it) */ static int term_columns(void) @@ -7,9 +7,6 @@ */ #include "cache.h" -#include <pwd.h> -#include <netdb.h> - static char git_default_date[50]; static void copy_gecos(struct passwd *w, char *name, int sz) @@ -158,12 +155,17 @@ static int copy(char *buf, int size, int offset, const char *src) static const char au_env[] = "GIT_AUTHOR_NAME"; static const char co_env[] = "GIT_COMMITTER_NAME"; static const char *env_hint = -"\n*** Environment problem:\n" +"\n" "*** Your name cannot be determined from your system services (gecos).\n" -"*** You would need to set %s and %s\n" -"*** environment variables; otherwise you won't be able to perform\n" -"*** certain operations because of \"empty ident\" errors.\n" -"*** Alternatively, you can use user.name configuration variable.\n\n"; +"\n" +"Run\n" +"\n" +" git repo-config user.email \"you@email.com\"\n" +" git repo-config user.name \"Your Name\"\n" +"\n" +"To set the identity in this repository.\n" +"Add --global to set your account\'s default\n" +"\n"; static const char *get_ident(const char *name, const char *email, const char *date_str, int error_on_no_name) @@ -216,3 +218,18 @@ const char *git_committer_info(int error_on_no_name) getenv("GIT_COMMITTER_DATE"), error_on_no_name); } + +void ignore_missing_committer_name() +{ + /* If we did not get a name from the user's gecos entry then + * git_default_name is empty; so instead load the username + * into it as a 'good enough for now' approximation of who + * this user is. + */ + if (!*git_default_name) { + struct passwd *pw = getpwuid(getuid()); + if (!pw) + die("You don't exist. Go away!"); + strlcpy(git_default_name, pw->pw_name, sizeof(git_default_name)); + } +} diff --git a/imap-send.c b/imap-send.c index a6a65680ee..ad91858bc4 100644 --- a/imap-send.c +++ b/imap-send.c @@ -24,13 +24,6 @@ #include "cache.h" -#include <assert.h> -#include <netinet/in.h> -#include <netinet/tcp.h> -#include <arpa/inet.h> -#include <sys/socket.h> -#include <netdb.h> - typedef struct store_conf { char *name; const char *path; /* should this be here? its interpretation is driver-specific */ @@ -103,8 +96,8 @@ typedef struct { static int Verbose, Quiet; -static void info( const char *, ... ); -static void warn( const char *, ... ); +static void imap_info( const char *, ... ); +static void imap_warn( const char *, ... ); static char *next_arg( char ** ); @@ -304,7 +297,7 @@ buffer_gets( buffer_t * b, char **s ) } static void -info( const char *msg, ... ) +imap_info( const char *msg, ... ) { va_list va; @@ -317,7 +310,7 @@ info( const char *msg, ... ) } static void -warn( const char *msg, ... ) +imap_warn( const char *msg, ... ) { va_list va; @@ -910,7 +903,7 @@ imap_open_store( imap_server_conf_t *srvc ) /* open connection to IMAP server */ if (srvc->tunnel) { - info( "Starting tunnel '%s'... ", srvc->tunnel ); + imap_info( "Starting tunnel '%s'... ", srvc->tunnel ); if (socketpair( PF_UNIX, SOCK_STREAM, 0, a )) { perror( "socketpair" ); @@ -933,31 +926,31 @@ imap_open_store( imap_server_conf_t *srvc ) imap->buf.sock.fd = a[1]; - info( "ok\n" ); + imap_info( "ok\n" ); } else { memset( &addr, 0, sizeof(addr) ); addr.sin_port = htons( srvc->port ); addr.sin_family = AF_INET; - info( "Resolving %s... ", srvc->host ); + imap_info( "Resolving %s... ", srvc->host ); he = gethostbyname( srvc->host ); if (!he) { perror( "gethostbyname" ); goto bail; } - info( "ok\n" ); + imap_info( "ok\n" ); addr.sin_addr.s_addr = *((int *) he->h_addr_list[0]); s = socket( PF_INET, SOCK_STREAM, 0 ); - info( "Connecting to %s:%hu... ", inet_ntoa( addr.sin_addr ), ntohs( addr.sin_port ) ); + imap_info( "Connecting to %s:%hu... ", inet_ntoa( addr.sin_addr ), ntohs( addr.sin_port ) ); if (connect( s, (struct sockaddr *)&addr, sizeof(addr) )) { close( s ); perror( "connect" ); goto bail; } - info( "ok\n" ); + imap_info( "ok\n" ); imap->buf.sock.fd = s; @@ -986,7 +979,7 @@ imap_open_store( imap_server_conf_t *srvc ) if (!preauth) { - info ("Logging in...\n"); + imap_info ("Logging in...\n"); if (!srvc->user) { fprintf( stderr, "Skipping server %s, no user\n", srvc->host ); goto bail; @@ -1013,7 +1006,7 @@ imap_open_store( imap_server_conf_t *srvc ) fprintf( stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host ); goto bail; } - warn( "*** IMAP Warning *** Password is being sent in the clear\n" ); + imap_warn( "*** IMAP Warning *** Password is being sent in the clear\n" ); if (imap_exec( ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass ) != RESP_OK) { fprintf( stderr, "IMAP error: LOGIN failed\n" ); goto bail; diff --git a/index-pack.c b/index-pack.c index 8331d99a62..5f6d128a83 100644 --- a/index-pack.c +++ b/index-pack.c @@ -6,8 +6,6 @@ #include "commit.h" #include "tag.h" #include "tree.h" -#include <sys/time.h> -#include <signal.h> static const char index_pack_usage[] = "git-index-pack [-v] [-o <index-file>] [{ ---keep | --keep=<msg> }] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }"; @@ -87,7 +85,7 @@ static unsigned display_progress(unsigned n, unsigned total, unsigned last_pc) static unsigned char input_buffer[4096]; static unsigned long input_offset, input_len, consumed_bytes; static SHA_CTX input_ctx; -static int input_fd, output_fd, mmap_fd; +static int input_fd, output_fd, pack_fd; /* Discard current buffer used content. */ static void flush(void) @@ -96,7 +94,7 @@ static void flush(void) if (output_fd >= 0) write_or_die(output_fd, input_buffer, input_offset); SHA1_Update(&input_ctx, input_buffer, input_offset); - memcpy(input_buffer, input_buffer + input_offset, input_len); + memmove(input_buffer, input_buffer + input_offset, input_len); input_offset = 0; } } @@ -148,14 +146,14 @@ static const char *open_pack_file(const char *pack_name) output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600); if (output_fd < 0) die("unable to create %s: %s\n", pack_name, strerror(errno)); - mmap_fd = output_fd; + pack_fd = output_fd; } else { input_fd = open(pack_name, O_RDONLY); if (input_fd < 0) die("cannot open packfile '%s': %s", pack_name, strerror(errno)); output_fd = -1; - mmap_fd = input_fd; + pack_fd = input_fd; } SHA1_Init(&input_ctx); return pack_name; @@ -268,7 +266,7 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_ case OBJ_TAG: break; default: - bad_object(obj->offset, "bad object type %d", obj->type); + bad_object(obj->offset, "unknown object type %d", obj->type); } obj->hdr_size = consumed_bytes - obj->offset; @@ -279,27 +277,25 @@ static void *get_data_from_pack(struct object_entry *obj) { unsigned long from = obj[0].offset + obj[0].hdr_size; unsigned long len = obj[1].offset - from; - unsigned pg_offset = from % getpagesize(); - unsigned char *map, *data; + unsigned char *src, *data; z_stream stream; int st; - map = mmap(NULL, len + pg_offset, PROT_READ, MAP_PRIVATE, - mmap_fd, from - pg_offset); - if (map == MAP_FAILED) - die("cannot mmap pack file: %s", strerror(errno)); + src = xmalloc(len); + if (pread(pack_fd, src, len, from) != len) + die("cannot pread pack file: %s", strerror(errno)); data = xmalloc(obj->size); memset(&stream, 0, sizeof(stream)); stream.next_out = data; stream.avail_out = obj->size; - stream.next_in = map + pg_offset; + stream.next_in = src; stream.avail_in = len; inflateInit(&stream); while ((st = inflate(&stream, Z_FINISH)) == Z_OK); inflateEnd(&stream); if (st != Z_STREAM_END || stream.total_out != obj->size) die("serious inflate inconsistency"); - munmap(map, len + pg_offset); + free(src); return data; } diff --git a/interpolate.c b/interpolate.c index 5d9d1889f0..f992ef7753 100644 --- a/interpolate.c +++ b/interpolate.c @@ -2,8 +2,6 @@ * Copyright 2006 Jon Loeliger */ -#include <string.h> - #include "git-compat-util.h" #include "interpolate.h" diff --git a/lockfile.c b/lockfile.c index 2a2fea3cb6..261baff049 100644 --- a/lockfile.c +++ b/lockfile.c @@ -1,7 +1,6 @@ /* * Copyright (c) 2005, Junio C Hamano */ -#include <signal.h> #include "cache.h" static struct lock_file *lock_file_list; diff --git a/log-tree.c b/log-tree.c index 8787df5cc6..35be33aaf7 100644 --- a/log-tree.c +++ b/log-tree.c @@ -114,6 +114,14 @@ void show_log(struct rev_info *opt, const char *sep) opt->loginfo = NULL; if (!opt->verbose_header) { + if (opt->left_right) { + if (commit->object.flags & BOUNDARY) + putchar('-'); + else if (commit->object.flags & SYMMETRIC_LEFT) + putchar('<'); + else + putchar('>'); + } fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout); if (opt->parents) show_parents(commit, abbrev_commit); @@ -192,10 +200,20 @@ void show_log(struct rev_info *opt, const char *sep) opt->diffopt.stat_sep = buffer; } } else { - printf("%s%s%s", - diff_get_color(opt->diffopt.color_diff, DIFF_COMMIT), - opt->commit_format == CMIT_FMT_ONELINE ? "" : "commit ", - diff_unique_abbrev(commit->object.sha1, abbrev_commit)); + fputs(diff_get_color(opt->diffopt.color_diff, DIFF_COMMIT), + stdout); + if (opt->commit_format != CMIT_FMT_ONELINE) + fputs("commit ", stdout); + if (opt->left_right) { + if (commit->object.flags & BOUNDARY) + putchar('-'); + else if (commit->object.flags & SYMMETRIC_LEFT) + putchar('<'); + else + putchar('>'); + } + fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), + stdout); if (opt->parents) show_parents(commit, abbrev_commit); if (parent) diff --git a/merge-base.c b/merge-base.c index 009caf804b..385f4ba386 100644 --- a/merge-base.c +++ b/merge-base.c @@ -1,4 +1,3 @@ -#include <stdlib.h> #include "cache.h" #include "commit.h" diff --git a/merge-file.c b/merge-file.c index fc9b148993..69dc1ebbf7 100644 --- a/merge-file.c +++ b/merge-file.c @@ -3,52 +3,6 @@ #include "xdiff-interface.h" #include "blob.h" -static void rm_temp_file(const char *filename) -{ - unlink(filename); - free((void *)filename); -} - -static const char *write_temp_file(mmfile_t *f) -{ - int fd; - const char *tmp = getenv("TMPDIR"); - char *filename; - - if (!tmp) - tmp = "/tmp"; - filename = mkpath("%s/%s", tmp, "git-tmp-XXXXXX"); - fd = mkstemp(filename); - if (fd < 0) - return NULL; - filename = xstrdup(filename); - if (f->size != xwrite(fd, f->ptr, f->size)) { - rm_temp_file(filename); - return NULL; - } - close(fd); - return filename; -} - -static void *read_temp_file(const char *filename, unsigned long *size) -{ - struct stat st; - char *buf = NULL; - int fd = open(filename, O_RDONLY); - if (fd < 0) - return NULL; - if (!fstat(fd, &st)) { - *size = st.st_size; - buf = xmalloc(st.st_size); - if (st.st_size != xread(fd, buf, st.st_size)) { - free(buf); - buf = NULL; - } - } - close(fd); - return buf; -} - static int fill_mmfile_blob(mmfile_t *f, struct blob *obj) { void *buf; @@ -72,22 +26,19 @@ static void free_mmfile(mmfile_t *f) static void *three_way_filemerge(mmfile_t *base, mmfile_t *our, mmfile_t *their, unsigned long *size) { - void *res; - const char *t1, *t2, *t3; - - t1 = write_temp_file(base); - t2 = write_temp_file(our); - t3 = write_temp_file(their); - res = NULL; - if (t1 && t2 && t3) { - int code = run_command("merge", t2, t1, t3, NULL); - if (!code || code == -1) - res = read_temp_file(t2, size); - } - rm_temp_file(t1); - rm_temp_file(t2); - rm_temp_file(t3); - return res; + mmbuffer_t res; + xpparam_t xpp; + int merge_status; + + memset(&xpp, 0, sizeof(xpp)); + merge_status = xdl_merge(base, our, ".our", their, ".their", + &xpp, XDL_MERGE_ZEALOUS, &res); + + if (merge_status < 0) + return NULL; + + *size = res.size; + return res.ptr; } static int common_outf(void *priv_, mmbuffer_t *mb, int nbuf) diff --git a/merge-index.c b/merge-index.c index 646d090c58..a9983dd78a 100644 --- a/merge-index.c +++ b/merge-index.c @@ -1,7 +1,3 @@ -#include <sys/types.h> -#include <sys/wait.h> -#include <signal.h> - #include "cache.h" static const char *pgm; diff --git a/merge-recursive.c b/merge-recursive.c index cd2cc77bf4..ca4f19e34d 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -3,13 +3,6 @@ * Fredrik Kuivinen. * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006 */ -#include <stdarg.h> -#include <string.h> -#include <assert.h> -#include <sys/wait.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <time.h> #include "cache.h" #include "cache-tree.h" #include "commit.h" @@ -21,6 +14,7 @@ #include "tag.h" #include "unpack-trees.h" #include "path-list.h" +#include "xdiff-interface.h" /* * A virtual commit has @@ -604,24 +598,21 @@ struct merge_file_info merge:1; }; -static char *git_unpack_file(const unsigned char *sha1, char *path) +static void fill_mm(const unsigned char *sha1, mmfile_t *mm) { - void *buf; - char type[20]; unsigned long size; - int fd; + char type[20]; - buf = read_sha1_file(sha1, type, &size); - if (!buf || strcmp(type, blob_type)) - die("unable to read blob object %s", sha1_to_hex(sha1)); + if (!hashcmp(sha1, null_sha1)) { + mm->ptr = xstrdup(""); + mm->size = 0; + return; + } - strcpy(path, ".merge_file_XXXXXX"); - fd = mkstemp(path); - if (fd < 0) - die("unable to create temp-file"); - flush_buffer(fd, buf, size); - close(fd); - return path; + mm->ptr = read_sha1_file(sha1, type, &size); + if (!mm->ptr || strcmp(type, blob_type)) + die("unable to read blob object %s", sha1_to_hex(sha1)); + mm->size = size; } static struct merge_file_info merge_file(struct diff_filespec *o, @@ -652,49 +643,41 @@ static struct merge_file_info merge_file(struct diff_filespec *o, else if (sha_eq(b->sha1, o->sha1)) hashcpy(result.sha, a->sha1); else if (S_ISREG(a->mode)) { - int code = 1, fd; - struct stat st; - char orig[PATH_MAX]; - char src1[PATH_MAX]; - char src2[PATH_MAX]; - const char *argv[] = { - "merge", "-L", NULL, "-L", NULL, "-L", NULL, - NULL, NULL, NULL, - NULL - }; - char *la, *lb, *lo; - - git_unpack_file(o->sha1, orig); - git_unpack_file(a->sha1, src1); - git_unpack_file(b->sha1, src2); - - argv[2] = la = xstrdup(mkpath("%s/%s", branch1, a->path)); - argv[6] = lb = xstrdup(mkpath("%s/%s", branch2, b->path)); - argv[4] = lo = xstrdup(mkpath("orig/%s", o->path)); - argv[7] = src1; - argv[8] = orig; - argv[9] = src2, - - code = run_command_v(10, argv); - - free(la); - free(lb); - free(lo); - if (code && code < -256) { - die("Failed to execute 'merge'. merge(1) is used as the " - "file-level merge tool. Is 'merge' in your path?"); - } - fd = open(src1, O_RDONLY); - if (fd < 0 || fstat(fd, &st) < 0 || - index_fd(result.sha, fd, &st, 1, - "blob")) - die("Unable to add %s to database", src1); - - unlink(orig); - unlink(src1); - unlink(src2); - - result.clean = WEXITSTATUS(code) == 0; + mmfile_t orig, src1, src2; + mmbuffer_t result_buf; + xpparam_t xpp; + char *name1, *name2; + int merge_status; + + name1 = xstrdup(mkpath("%s:%s", branch1, a->path)); + name2 = xstrdup(mkpath("%s:%s", branch2, b->path)); + + fill_mm(o->sha1, &orig); + fill_mm(a->sha1, &src1); + fill_mm(b->sha1, &src2); + + memset(&xpp, 0, sizeof(xpp)); + merge_status = xdl_merge(&orig, + &src1, name1, + &src2, name2, + &xpp, XDL_MERGE_ZEALOUS, + &result_buf); + free(name1); + free(name2); + free(orig.ptr); + free(src1.ptr); + free(src2.ptr); + + if ((merge_status < 0) || !result_buf.ptr) + die("Failed to execute internal merge"); + + if (write_sha1_file(result_buf.ptr, result_buf.size, + blob_type, result.sha)) + die("Unable to add %s to database", + a->path); + + free(result_buf.ptr); + result.clean = (merge_status == 0); } else { if (!(S_ISLNK(a->mode) || S_ISLNK(b->mode))) die("cannot merge modes?"); @@ -889,7 +872,7 @@ static int process_renames(struct path_list *a_renames, struct diff_filespec src_other, dst_other; int try_merge, stage = a_renames == renames1 ? 3: 2; - remove_file(1, ren1_src, 1); + remove_file(1, ren1_src, index_only); hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha); src_other.mode = ren1->src_entry->stages[stage].mode; @@ -1061,38 +1044,17 @@ static int process_entry(const char *path, struct stage_data *entry, output("Adding %s", path); update_file(1, sha, mode, path); } - } else if (!o_sha && a_sha && b_sha) { - /* Case C: Added in both (check for same permissions). */ - if (sha_eq(a_sha, b_sha)) { - if (a_mode != b_mode) { - clean_merge = 0; - output("CONFLICT: File %s added identically in both branches, " - "but permissions conflict %06o->%06o", - path, a_mode, b_mode); - output("CONFLICT: adding with permission: %06o", a_mode); - update_file(0, a_sha, a_mode, path); - } else { - /* This case is handled by git-read-tree */ - assert(0 && "This case must be handled by git-read-tree"); - } - } else { - const char *new_path1, *new_path2; - clean_merge = 0; - new_path1 = unique_path(path, branch1); - new_path2 = unique_path(path, branch2); - output("CONFLICT (add/add): File %s added non-identically " - "in both branches. Adding as %s and %s instead.", - path, new_path1, new_path2); - remove_file(0, path, 0); - update_file(0, a_sha, a_mode, new_path1); - update_file(0, b_sha, b_mode, new_path2); - } - - } else if (o_sha && a_sha && b_sha) { + } else if (a_sha && b_sha) { + /* Case C: Added in both (check for same permissions) and */ /* case D: Modified in both, but differently. */ + const char *reason = "content"; struct merge_file_info mfi; struct diff_filespec o, a, b; + if (!o_sha) { + reason = "add/add"; + o_sha = (unsigned char *)null_sha1; + } output("Auto-merging %s", path); o.path = a.path = b.path = (char *)path; hashcpy(o.sha1, o_sha); @@ -1109,7 +1071,8 @@ static int process_entry(const char *path, struct stage_data *entry, update_file(1, mfi.sha, mfi.mode, path); else { clean_merge = 0; - output("CONFLICT (content): Merge conflict in %s", path); + output("CONFLICT (%s): Merge conflict in %s", + reason, path); if (index_only) update_file(0, mfi.sha, mfi.mode, path); @@ -1238,7 +1201,7 @@ static int merge(struct commit *h1, tree->object.parsed = 1; tree->object.type = OBJ_TREE; - hash_sha1_file(NULL, 0, tree_type, tree->object.sha1); + write_sha1_file(NULL, 0, tree_type, tree->object.sha1); merged_common_ancestors = make_virtual_commit(tree, "ancestor"); } @@ -1300,6 +1263,18 @@ static struct commit *get_ref(const char *ref) return (struct commit *)object; } +static const char *better_branch_name(const char *branch) +{ + static char githead_env[8 + 40 + 1]; + char *name; + + if (strlen(branch) != 40) + return branch; + sprintf(githead_env, "GITHEAD_%s", branch); + name = getenv(githead_env); + return name ? name : branch; +} + int main(int argc, char *argv[]) { static const char *bases[2]; @@ -1309,7 +1284,7 @@ int main(int argc, char *argv[]) struct commit *result, *h1, *h2; git_config(git_default_config); /* core.filemode */ - original_index_file = getenv("GIT_INDEX_FILE"); + original_index_file = getenv(INDEX_ENVIRONMENT); if (!original_index_file) original_index_file = xstrdup(git_path("index")); @@ -1330,11 +1305,14 @@ int main(int argc, char *argv[]) branch1 = argv[++i]; branch2 = argv[++i]; - printf("Merging %s with %s\n", branch1, branch2); h1 = get_ref(branch1); h2 = get_ref(branch2); + branch1 = better_branch_name(branch1); + branch2 = better_branch_name(branch2); + printf("Merging %s with %s\n", branch1, branch2); + if (bases_count == 1) { struct commit *ancestor = get_ref(bases[0]); clean = merge(h1, h2, branch1, branch2, 0, ancestor, &result); diff --git a/patch-delta.c b/patch-delta.c index e3a1d425ee..ed9db81fa8 100644 --- a/patch-delta.c +++ b/patch-delta.c @@ -9,8 +9,7 @@ * published by the Free Software Foundation. */ -#include <stdlib.h> -#include <string.h> +#include "git-compat-util.h" #include "delta.h" void *patch_delta(const void *src_buf, unsigned long src_size, @@ -34,9 +33,7 @@ void *patch_delta(const void *src_buf, unsigned long src_size, /* now the result size */ size = get_delta_hdr_size(&data, top); - dst_buf = malloc(size + 1); - if (!dst_buf) - return NULL; + dst_buf = xmalloc(size + 1); dst_buf[size] = 0; out = dst_buf; @@ -55,13 +52,13 @@ void *patch_delta(const void *src_buf, unsigned long src_size, if (cp_off + cp_size < cp_size || cp_off + cp_size > src_size || cp_size > size) - goto bad; + break; memcpy(out, (char *) src_buf + cp_off, cp_size); out += cp_size; size -= cp_size; } else if (cmd) { if (cmd > size) - goto bad; + break; memcpy(out, data, cmd); out += cmd; data += cmd; @@ -72,12 +69,14 @@ void *patch_delta(const void *src_buf, unsigned long src_size, * extensions. In the mean time we must fail when * encountering them (might be data corruption). */ + error("unexpected delta opcode 0"); goto bad; } } /* sanity check */ if (data != top || size != 0) { + error("delta replay has gone wild"); bad: free(dst_buf); return NULL; diff --git a/path-list.c b/path-list.c index f8800f8e66..caaa5cc57b 100644 --- a/path-list.c +++ b/path-list.c @@ -1,4 +1,3 @@ -#include <stdio.h> #include "cache.h" #include "path-list.h" @@ -11,7 +11,6 @@ * which is what it's designed for. */ #include "cache.h" -#include <pwd.h> static char bad_path[] = "/bad-path/"; diff --git a/perl/.gitignore b/perl/.gitignore index e990caeea7..98b24772c7 100644 --- a/perl/.gitignore +++ b/perl/.gitignore @@ -1,4 +1,5 @@ -Makefile +perl.mak +perl.mak.old blib blibdirs pm_to_blib diff --git a/perl/Makefile b/perl/Makefile new file mode 100644 index 0000000000..099beda873 --- /dev/null +++ b/perl/Makefile @@ -0,0 +1,39 @@ +# +# Makefile for perl support modules and routine +# +makfile:=perl.mak + +PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) +prefix_SQ = $(subst ','\'',$(prefix)) + +all install instlibdir: $(makfile) + $(MAKE) -f $(makfile) $@ + +clean: + test -f $(makfile) && $(MAKE) -f $(makfile) $@ || exit 0 + $(RM) ppport.h + $(RM) $(makfile) + $(RM) $(makfile).old + +ifdef NO_PERL_MAKEMAKER +instdir_SQ = $(subst ','\'',$(prefix)/lib) +$(makfile): ../GIT-CFLAGS Makefile + echo all: > $@ + echo ' :' >> $@ + echo install: >> $@ + echo ' mkdir -p $(instdir_SQ)' >> $@ + echo ' $(RM) $(instdir_SQ)/Git.pm; cp Git.pm $(instdir_SQ)' >> $@ + echo ' $(RM) $(instdir_SQ)/Error.pm; \ + cp private-Error.pm $(instdir_SQ)/Error.pm' >> $@ + echo instlibdir: >> $@ + echo ' echo $(instdir_SQ)' >> $@ +else +$(makfile): Makefile.PL ../GIT-CFLAGS + '$(PERL_PATH_SQ)' $< PREFIX='$(prefix_SQ)' +endif + +# this is just added comfort for calling make directly in perl dir +# (even though GIT-CFLAGS aren't used yet. If ever) +../GIT-CFLAGS: + $(MAKE) -C .. GIT-CFLAGS + diff --git a/perl/Makefile.PL b/perl/Makefile.PL index de73235e4c..41687757a7 100644 --- a/perl/Makefile.PL +++ b/perl/Makefile.PL @@ -24,5 +24,6 @@ WriteMakefile( NAME => 'Git', VERSION_FROM => 'Git.pm', PM => \%pm, + MAKEFILE => 'perl.mak', %extra ); diff --git a/pkt-line.c b/pkt-line.c index c1e81f976f..b4cb7e2756 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -72,7 +72,7 @@ static void safe_read(int fd, void *buffer, unsigned size) if (ret < 0) die("read error (%s)", strerror(errno)); if (!ret) - die("unexpected EOF"); + die("The remote end hung up unexpectedly"); n += ret; } } diff --git a/read-cache.c b/read-cache.c index eae4745d28..b8d83ccd9f 100644 --- a/read-cache.c +++ b/read-cache.c @@ -358,7 +358,7 @@ int add_file_to_index(const char *path, int verbose) if (index_path(ce->sha1, path, &st, 1)) die("unable to index file %s", path); - if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD)) + if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE)) die("unable to add %s to index",path); if (verbose) printf("add '%s'\n", path); @@ -517,7 +517,7 @@ static int has_dir_name(const struct cache_entry *ce, int pos, int ok_to_replace pos = cache_name_pos(name, ntohs(create_ce_flags(len, stage))); if (pos >= 0) { retval = -1; - if (ok_to_replace) + if (!ok_to_replace) break; remove_cache_entry_at(pos); continue; @@ -609,7 +609,7 @@ int add_cache_entry(struct cache_entry *ce, int option) if (!skip_df_check && check_file_directory_conflict(ce, pos, ok_to_replace)) { if (!ok_to_replace) - return -1; + return error("'%s' appears as both a file and as a directory", ce->name); pos = cache_name_pos(ce->name, ntohs(ce->ce_flags)); pos = -pos-1; } diff --git a/receive-pack.c b/receive-pack.c index d56898c9b2..59b682c03a 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -6,15 +6,14 @@ #include "exec_cmd.h" #include "commit.h" #include "object.h" -#include <sys/wait.h> static const char receive_pack_usage[] = "git-receive-pack <git-dir>"; static int deny_non_fast_forwards = 0; -static int unpack_limit = 5000; +static int unpack_limit = 100; static int report_status; -static char capabilities[] = "report-status"; +static char capabilities[] = " report-status delete-refs "; static int capabilities_sent; static int receive_pack_config(const char *var, const char *value) @@ -113,12 +112,15 @@ static int update(struct command *cmd) strcpy(new_hex, sha1_to_hex(new_sha1)); strcpy(old_hex, sha1_to_hex(old_sha1)); - if (!has_sha1_file(new_sha1)) { + + if (!is_null_sha1(new_sha1) && !has_sha1_file(new_sha1)) { cmd->error_string = "bad pack"; return error("unpack should have generated %s, " "but I can't find it!", new_hex); } - if (deny_non_fast_forwards && !is_null_sha1(old_sha1)) { + if (deny_non_fast_forwards && !is_null_sha1(new_sha1) && + !is_null_sha1(old_sha1) && + !strncmp(name, "refs/heads/", 11)) { struct commit *old_commit, *new_commit; struct commit_list *bases, *ent; @@ -138,14 +140,22 @@ static int update(struct command *cmd) return error("hook declined to update %s", name); } - lock = lock_any_ref_for_update(name, old_sha1); - if (!lock) { - cmd->error_string = "failed to lock"; - return error("failed to lock %s", name); + if (is_null_sha1(new_sha1)) { + if (delete_ref(name, old_sha1)) { + cmd->error_string = "failed to delete"; + return error("failed to delete %s", name); + } + fprintf(stderr, "%s: %s -> deleted\n", name, old_hex); + } + else { + lock = lock_any_ref_for_update(name, old_sha1); + if (!lock) { + cmd->error_string = "failed to lock"; + return error("failed to lock %s", name); + } + write_ref_sha1(lock, new_sha1, "push"); + fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex); } - write_ref_sha1(lock, new_sha1, "push"); - - fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex); return 0; } @@ -375,6 +385,16 @@ static void report(const char *unpack_status) packet_flush(1); } +static int delete_only(struct command *cmd) +{ + while (cmd) { + if (!is_null_sha1(cmd->new_sha1)) + return 0; + cmd = cmd->next; + } + return 1; +} + int main(int argc, char **argv) { int i; @@ -399,6 +419,8 @@ int main(int argc, char **argv) die("'%s': unable to chdir or not a git archive", dir); setup_ident(); + /* don't die if gecos is empty */ + ignore_missing_committer_name(); git_config(receive_pack_config); write_head_info(); @@ -408,7 +430,10 @@ int main(int argc, char **argv) read_head_info(); if (commands) { - const char *unpack_status = unpack(); + const char *unpack_status = NULL; + + if (!delete_only(commands)) + unpack_status = unpack(); if (!unpack_status) execute_commands(); if (pack_lockfile) @@ -1,12 +1,16 @@ -#include "refs.h" #include "cache.h" +#include "refs.h" +#include "object.h" +#include "tag.h" -#include <errno.h> +/* ISSYMREF=01 and ISPACKED=02 are public interfaces */ +#define REF_KNOWS_PEELED 04 struct ref_list { struct ref_list *next; unsigned char flag; /* ISSYMREF? ISPACKED? */ unsigned char sha1[20]; + unsigned char peeled[20]; char name[FLEX_ARRAY]; }; @@ -34,11 +38,13 @@ static const char *parse_ref_line(char *line, unsigned char *sha1) if (line[len] != '\n') return NULL; line[len] = 0; + return line; } static struct ref_list *add_ref(const char *name, const unsigned char *sha1, - int flag, struct ref_list *list) + int flag, struct ref_list *list, + struct ref_list **new_entry) { int len; struct ref_list **p = &list, *entry; @@ -50,8 +56,11 @@ static struct ref_list *add_ref(const char *name, const unsigned char *sha1, break; /* Same as existing entry? */ - if (!cmp) + if (!cmp) { + if (new_entry) + *new_entry = entry; return list; + } p = &entry->next; } @@ -59,10 +68,13 @@ static struct ref_list *add_ref(const char *name, const unsigned char *sha1, len = strlen(name) + 1; entry = xmalloc(sizeof(struct ref_list) + len); hashcpy(entry->sha1, sha1); + hashclr(entry->peeled); memcpy(entry->name, name, len); entry->flag = flag; entry->next = *p; *p = entry; + if (new_entry) + *new_entry = entry; return list; } @@ -98,25 +110,50 @@ static void invalidate_cached_refs(void) ca->did_loose = ca->did_packed = 0; } +static void read_packed_refs(FILE *f, struct cached_refs *cached_refs) +{ + struct ref_list *list = NULL; + struct ref_list *last = NULL; + char refline[PATH_MAX]; + int flag = REF_ISPACKED; + + while (fgets(refline, sizeof(refline), f)) { + unsigned char sha1[20]; + const char *name; + static const char header[] = "# pack-refs with:"; + + if (!strncmp(refline, header, sizeof(header)-1)) { + const char *traits = refline + sizeof(header) - 1; + if (strstr(traits, " peeled ")) + flag |= REF_KNOWS_PEELED; + /* perhaps other traits later as well */ + continue; + } + + name = parse_ref_line(refline, sha1); + if (name) { + list = add_ref(name, sha1, flag, list, &last); + continue; + } + if (last && + refline[0] == '^' && + strlen(refline) == 42 && + refline[41] == '\n' && + !get_sha1_hex(refline + 1, sha1)) + hashcpy(last->peeled, sha1); + } + cached_refs->packed = list; +} + static struct ref_list *get_packed_refs(void) { if (!cached_refs.did_packed) { - struct ref_list *refs = NULL; FILE *f = fopen(git_path("packed-refs"), "r"); + cached_refs.packed = NULL; if (f) { - struct ref_list *list = NULL; - char refline[PATH_MAX]; - while (fgets(refline, sizeof(refline), f)) { - unsigned char sha1[20]; - const char *name = parse_ref_line(refline, sha1); - if (!name) - continue; - list = add_ref(name, sha1, REF_ISPACKED, list); - } + read_packed_refs(f, &cached_refs); fclose(f); - refs = list; } - cached_refs.packed = refs; cached_refs.did_packed = 1; } return cached_refs.packed; @@ -159,7 +196,7 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) error("%s points nowhere!", ref); continue; } - list = add_ref(ref, sha1, flag, list); + list = add_ref(ref, sha1, flag, list, NULL); } free(ref); closedir(dir); @@ -336,6 +373,43 @@ static int do_one_ref(const char *base, each_ref_fn fn, int trim, return fn(entry->name + trim, entry->sha1, entry->flag, cb_data); } +int peel_ref(const char *ref, unsigned char *sha1) +{ + int flag; + unsigned char base[20]; + struct object *o; + + if (!resolve_ref(ref, base, 1, &flag)) + return -1; + + if ((flag & REF_ISPACKED)) { + struct ref_list *list = get_packed_refs(); + + while (list) { + if (!strcmp(list->name, ref)) { + if (list->flag & REF_KNOWS_PEELED) { + hashcpy(sha1, list->peeled); + return 0; + } + /* older pack-refs did not leave peeled ones */ + break; + } + list = list->next; + } + } + + /* fallback - callers should not call this for unpacked refs */ + o = parse_object(base); + if (o->type == OBJ_TAG) { + o = deref_tag(o, ref, 0); + if (o) { + hashcpy(sha1, o->sha1); + return 0; + } + } + return -1; +} + static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, void *cb_data) { @@ -458,7 +532,7 @@ int check_ref_format(const char *ref) level++; if (!ch) { if (level < 2) - return -1; /* at least of form "heads/blah" */ + return -2; /* at least of form "heads/blah" */ return 0; } } @@ -534,6 +608,29 @@ static int remove_empty_directories(char *file) return remove_empty_dir_recursive(path, len); } +static int is_refname_available(const char *ref, const char *oldref, + struct ref_list *list, int quiet) +{ + int namlen = strlen(ref); /* e.g. 'foo/bar' */ + while (list) { + /* list->name could be 'foo' or 'foo/bar/baz' */ + if (!oldref || strcmp(oldref, list->name)) { + int len = strlen(list->name); + int cmplen = (namlen < len) ? namlen : len; + const char *lead = (namlen < len) ? list->name : ref; + if (!strncmp(ref, list->name, cmplen) && + lead[cmplen] == '/') { + if (!quiet) + error("'%s' exists; cannot create '%s'", + list->name, ref); + return 0; + } + } + list = list->next; + } + return 1; +} + static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int *flag) { char *ref_file; @@ -567,29 +664,14 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char orig_ref, strerror(errno)); goto error_return; } - if (is_null_sha1(lock->old_sha1)) { - /* The ref did not exist and we are creating it. - * Make sure there is no existing ref that is packed - * whose name begins with our refname, nor a ref whose - * name is a proper prefix of our refname. - */ - int namlen = strlen(ref); /* e.g. 'foo/bar' */ - struct ref_list *list = get_packed_refs(); - while (list) { - /* list->name could be 'foo' or 'foo/bar/baz' */ - int len = strlen(list->name); - int cmplen = (namlen < len) ? namlen : len; - const char *lead = (namlen < len) ? list->name : ref; - - if (!strncmp(ref, list->name, cmplen) && - lead[cmplen] == '/') { - error("'%s' exists; cannot create '%s'", - list->name, ref); - goto error_return; - } - list = list->next; - } - } + /* When the ref did not exist and we are creating it, + * make sure there is no existing ref that is packed + * whose name begins with our refname, nor a ref whose + * name is a proper prefix of our refname. + */ + if (is_null_sha1(lock->old_sha1) && + !is_refname_available(ref, NULL, get_packed_refs(), 0)) + goto error_return; lock->lk = xcalloc(1, sizeof(struct lock_file)); @@ -700,6 +782,127 @@ int delete_ref(const char *refname, unsigned char *sha1) return ret; } +int rename_ref(const char *oldref, const char *newref, const char *logmsg) +{ + static const char renamed_ref[] = "RENAMED-REF"; + unsigned char sha1[20], orig_sha1[20]; + int flag = 0, logmoved = 0; + struct ref_lock *lock; + struct stat loginfo; + int log = !lstat(git_path("logs/%s", oldref), &loginfo); + + if (S_ISLNK(loginfo.st_mode)) + return error("reflog for %s is a symlink", oldref); + + if (!resolve_ref(oldref, orig_sha1, 1, &flag)) + return error("refname %s not found", oldref); + + if (!is_refname_available(newref, oldref, get_packed_refs(), 0)) + return 1; + + if (!is_refname_available(newref, oldref, get_loose_refs(), 0)) + return 1; + + lock = lock_ref_sha1_basic(renamed_ref, NULL, NULL); + if (!lock) + return error("unable to lock %s", renamed_ref); + lock->force_write = 1; + if (write_ref_sha1(lock, orig_sha1, logmsg)) + return error("unable to save current sha1 in %s", renamed_ref); + + if (log && rename(git_path("logs/%s", oldref), git_path("tmp-renamed-log"))) + return error("unable to move logfile logs/%s to tmp-renamed-log: %s", + oldref, strerror(errno)); + + if (delete_ref(oldref, orig_sha1)) { + error("unable to delete old %s", oldref); + goto rollback; + } + + if (resolve_ref(newref, sha1, 1, &flag) && delete_ref(newref, sha1)) { + if (errno==EISDIR) { + if (remove_empty_directories(git_path("%s", newref))) { + error("Directory not empty: %s", newref); + goto rollback; + } + } else { + error("unable to delete existing %s", newref); + goto rollback; + } + } + + if (log && safe_create_leading_directories(git_path("logs/%s", newref))) { + error("unable to create directory for %s", newref); + goto rollback; + } + + retry: + if (log && rename(git_path("tmp-renamed-log"), git_path("logs/%s", newref))) { + if (errno==EISDIR) { + if (remove_empty_directories(git_path("logs/%s", newref))) { + error("Directory not empty: logs/%s", newref); + goto rollback; + } + goto retry; + } else { + error("unable to move logfile tmp-renamed-log to logs/%s: %s", + newref, strerror(errno)); + goto rollback; + } + } + logmoved = log; + + lock = lock_ref_sha1_basic(newref, NULL, NULL); + if (!lock) { + error("unable to lock %s for update", newref); + goto rollback; + } + + lock->force_write = 1; + hashcpy(lock->old_sha1, orig_sha1); + if (write_ref_sha1(lock, orig_sha1, logmsg)) { + error("unable to write current sha1 into %s", newref); + goto rollback; + } + + if (!strncmp(oldref, "refs/heads/", 11) && + !strncmp(newref, "refs/heads/", 11)) { + char oldsection[1024], newsection[1024]; + + snprintf(oldsection, 1024, "branch.%s", oldref + 11); + snprintf(newsection, 1024, "branch.%s", newref + 11); + if (git_config_rename_section(oldsection, newsection) < 0) + return 1; + } + + return 0; + + rollback: + lock = lock_ref_sha1_basic(oldref, NULL, NULL); + if (!lock) { + error("unable to lock %s for rollback", oldref); + goto rollbacklog; + } + + lock->force_write = 1; + flag = log_all_ref_updates; + log_all_ref_updates = 0; + if (write_ref_sha1(lock, orig_sha1, NULL)) + error("unable to write current sha1 into %s", oldref); + log_all_ref_updates = flag; + + rollbacklog: + if (logmoved && rename(git_path("logs/%s", newref), git_path("logs/%s", oldref))) + error("unable to restore logfile %s from %s: %s", + oldref, newref, strerror(errno)); + if (!logmoved && log && + rename(git_path("tmp-renamed-log"), git_path("logs/%s", oldref))) + error("unable to restore logfile %s from tmp-renamed-log: %s", + oldref, strerror(errno)); + + return 1; +} + void unlock_ref(struct ref_lock *lock) { if (lock->lock_fd >= 0) { @@ -810,7 +1013,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char * { const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec; char *tz_c; - int logfd, tz; + int logfd, tz, reccnt = 0; struct stat st; unsigned long date; unsigned char logged_sha1[20]; @@ -828,6 +1031,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char * lastrec = NULL; rec = logend = logdata + st.st_size; while (logdata < rec) { + reccnt++; if (logdata < rec && *(rec-1) == '\n') rec--; lastgt = NULL; @@ -884,7 +1088,37 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char * if (get_sha1_hex(logdata, sha1)) die("Log %s is corrupt.", logfile); munmap((void*)logdata, st.st_size); - fprintf(stderr, "warning: Log %s only goes back to %s.\n", - logfile, show_rfc2822_date(date, tz)); + if (at_time) + fprintf(stderr, "warning: Log %s only goes back to %s.\n", + logfile, show_rfc2822_date(date, tz)); + else + fprintf(stderr, "warning: Log %s only has %d entries.\n", + logfile, reccnt); return 0; } + +void for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data) +{ + const char *logfile; + FILE *logfp; + char buf[1024]; + + logfile = git_path("logs/%s", ref); + logfp = fopen(logfile, "r"); + if (!logfp) + return; + while (fgets(buf, sizeof(buf), logfp)) { + unsigned char osha1[20], nsha1[20]; + int len; + + /* old SP new SP name <email> SP time TAB msg LF */ + len = strlen(buf); + if (len < 83 || buf[len-1] != '\n' || + get_sha1_hex(buf, osha1) || buf[40] != ' ' || + get_sha1_hex(buf + 41, nsha1) || buf[81] != ' ') + continue; /* corrupt? */ + fn(osha1, nsha1, buf+82, cb_data); + } + fclose(logfp); +} + @@ -10,12 +10,13 @@ struct ref_lock { int force_write; }; +#define REF_ISSYMREF 01 +#define REF_ISPACKED 02 + /* * Calls the specified function for each ref file until it returns nonzero, * and returns the value */ -#define REF_ISSYMREF 01 -#define REF_ISPACKED 02 typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data); extern int head_ref(each_ref_fn, void *); extern int for_each_ref(each_ref_fn, void *); @@ -23,6 +24,8 @@ extern int for_each_tag_ref(each_ref_fn, void *); extern int for_each_branch_ref(each_ref_fn, void *); extern int for_each_remote_ref(each_ref_fn, void *); +extern int peel_ref(const char *, unsigned char *); + /** Reads the refs file specified into sha1 **/ extern int get_ref_sha1(const char *ref, unsigned char *sha1); @@ -41,7 +44,14 @@ extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, cons /** Reads log for the value of ref during at_time. **/ extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1); +/* iterate over reflog entries */ +typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, char *, void *); +void for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data); + /** Returns 0 if target has the right format for a ref. **/ extern int check_ref_format(const char *target); +/** rename ref, return 0 on success **/ +extern int rename_ref(const char *oldref, const char *newref, const char *logmsg); + #endif /* REFS_H */ diff --git a/revision.c b/revision.c index 993bb668a2..af9f87418c 100644 --- a/revision.c +++ b/revision.c @@ -6,7 +6,6 @@ #include "diff.h" #include "refs.h" #include "revision.h" -#include <regex.h> #include "grep.h" static char *path_name(struct name_path *path, const char *name) @@ -344,6 +343,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit) static void add_parents_to_list(struct rev_info *revs, struct commit *commit, struct commit_list **list) { struct commit_list *parent = commit->parents; + unsigned left_flag; if (commit->object.flags & ADDED) return; @@ -388,6 +388,7 @@ static void add_parents_to_list(struct rev_info *revs, struct commit *commit, st if (revs->no_walk) return; + left_flag = (commit->object.flags & SYMMETRIC_LEFT); parent = commit->parents; while (parent) { struct commit *p = parent->item; @@ -395,6 +396,7 @@ static void add_parents_to_list(struct rev_info *revs, struct commit *commit, st parent = parent->next; parse_commit(p); + p->object.flags |= left_flag; if (p->object.flags & SEEN) continue; p->object.flags |= SEEN; @@ -462,21 +464,69 @@ static void limit_list(struct rev_info *revs) revs->commits = newlist; } -static int all_flags; -static struct rev_info *all_revs; +struct all_refs_cb { + int all_flags; + int warned_bad_reflog; + struct rev_info *all_revs; + const char *name_for_errormsg; +}; static int handle_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) { - struct object *object = get_reference(all_revs, path, sha1, all_flags); - add_pending_object(all_revs, object, ""); + struct all_refs_cb *cb = cb_data; + struct object *object = get_reference(cb->all_revs, path, sha1, + cb->all_flags); + add_pending_object(cb->all_revs, object, ""); return 0; } static void handle_all(struct rev_info *revs, unsigned flags) { - all_revs = revs; - all_flags = flags; - for_each_ref(handle_one_ref, NULL); + struct all_refs_cb cb; + cb.all_revs = revs; + cb.all_flags = flags; + for_each_ref(handle_one_ref, &cb); +} + +static void handle_one_reflog_commit(unsigned char *sha1, void *cb_data) +{ + struct all_refs_cb *cb = cb_data; + if (!is_null_sha1(sha1)) { + struct object *o = parse_object(sha1); + if (o) { + o->flags |= cb->all_flags; + add_pending_object(cb->all_revs, o, ""); + } + else if (!cb->warned_bad_reflog) { + warn("reflog of '%s' references pruned commits", + cb->name_for_errormsg); + cb->warned_bad_reflog = 1; + } + } +} + +static int handle_one_reflog_ent(unsigned char *osha1, unsigned char *nsha1, char *detail, void *cb_data) +{ + handle_one_reflog_commit(osha1, cb_data); + handle_one_reflog_commit(nsha1, cb_data); + return 0; +} + +static int handle_one_reflog(const char *path, const unsigned char *sha1, int flag, void *cb_data) +{ + struct all_refs_cb *cb = cb_data; + cb->warned_bad_reflog = 0; + cb->name_for_errormsg = path; + for_each_reflog_ent(path, handle_one_reflog_ent, cb_data); + return 0; +} + +static void handle_reflog(struct rev_info *revs, unsigned flags) +{ + struct all_refs_cb cb; + cb.all_revs = revs; + cb.all_flags = flags; + for_each_ref(handle_one_reflog, &cb); } static int add_parents_only(struct rev_info *revs, const char *arg, int flags) @@ -524,6 +574,7 @@ void init_revisions(struct rev_info *revs, const char *prefix) revs->prefix = prefix; revs->max_age = -1; revs->min_age = -1; + revs->skip_count = -1; revs->max_count = -1; revs->prune_fn = NULL; @@ -640,7 +691,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, add_pending_commit_list(revs, exclude, flags_exclude); free_commit_list(exclude); - a->object.flags |= flags; + a->object.flags |= flags | SYMMETRIC_LEFT; } else a->object.flags |= flags_exclude; b->object.flags |= flags; @@ -760,6 +811,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->max_count = atoi(arg + 12); continue; } + if (!strncmp(arg, "--skip=", 7)) { + revs->skip_count = atoi(arg + 7); + continue; + } /* accept -<digit>, like traditional "head" */ if ((*arg == '-') && isdigit(arg[1])) { revs->max_count = atoi(arg + 1); @@ -803,6 +858,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch handle_all(revs, flags); continue; } + if (!strcmp(arg, "--reflog")) { + handle_reflog(revs, flags); + continue; + } if (!strcmp(arg, "--not")) { flags ^= UNINTERESTING; continue; @@ -850,6 +909,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->boundary = 1; continue; } + if (!strcmp(arg, "--left-right")) { + revs->left_right = 1; + continue; + } if (!strcmp(arg, "--objects")) { revs->tag_objects = 1; revs->tree_objects = 1; @@ -1123,22 +1186,10 @@ static int commit_match(struct commit *commit, struct rev_info *opt) commit->buffer, strlen(commit->buffer)); } -struct commit *get_revision(struct rev_info *revs) +static struct commit *get_revision_1(struct rev_info *revs) { - struct commit_list *list = revs->commits; - - if (!list) - return NULL; - - /* Check the max_count ... */ - switch (revs->max_count) { - case -1: - break; - case 0: + if (!revs->commits) return NULL; - default: - revs->max_count--; - } do { struct commit_list *entry = revs->commits; @@ -1206,3 +1257,28 @@ struct commit *get_revision(struct rev_info *revs) } while (revs->commits); return NULL; } + +struct commit *get_revision(struct rev_info *revs) +{ + struct commit *c = NULL; + + if (0 < revs->skip_count) { + while ((c = get_revision_1(revs)) != NULL) { + if (revs->skip_count-- <= 0) + break; + } + } + + /* Check the max_count ... */ + switch (revs->max_count) { + case -1: + break; + case 0: + return NULL; + default: + revs->max_count--; + } + if (c) + return c; + return get_revision_1(revs); +} diff --git a/revision.h b/revision.h index 3adab9590a..ec991e5c57 100644 --- a/revision.h +++ b/revision.h @@ -9,6 +9,7 @@ #define BOUNDARY (1u<<5) #define BOUNDARY_SHOW (1u<<6) #define ADDED (1u<<7) /* Parents already parsed and added? */ +#define SYMMETRIC_LEFT (1u<<8) struct rev_info; struct log_info; @@ -40,6 +41,7 @@ struct rev_info { limited:1, unpacked:1, /* see also ignore_packed below */ boundary:1, + left_right:1, parents:1; /* Diff flags */ @@ -75,6 +77,7 @@ struct rev_info { struct grep_opt *grep_filter; /* special limits */ + int skip_count; int max_count; unsigned long max_age; unsigned long min_age; @@ -1,10 +1,6 @@ -#include <string.h> -#include <sys/types.h> -#include <sys/socket.h> - +#include "cache.h" #include "rsh.h" #include "quote.h" -#include "cache.h" #define COMMAND_SIZE 4096 diff --git a/run-command.c b/run-command.c index 61908682b9..492ad3e64c 100644 --- a/run-command.c +++ b/run-command.c @@ -1,6 +1,5 @@ #include "cache.h" #include "run-command.h" -#include <sys/wait.h> #include "exec_cmd.h" int run_command_v_opt(int argc, const char **argv, int flags) diff --git a/send-pack.c b/send-pack.c index 447666665b..cc884f3b2d 100644 --- a/send-pack.c +++ b/send-pack.c @@ -271,6 +271,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) int new_refs; int ret = 0; int ask_for_status_report = 0; + int allow_deleting_refs = 0; int expect_status_report = 0; /* No funny business with the matcher */ @@ -280,6 +281,8 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) /* Does the other end support the reporting? */ if (server_supports("report-status")) ask_for_status_report = 1; + if (server_supports("delete-refs")) + allow_deleting_refs = 1; /* match them up */ if (!remote_tail) @@ -299,9 +302,19 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) new_refs = 0; for (ref = remote_refs; ref; ref = ref->next) { char old_hex[60], *new_hex; + int delete_ref; + if (!ref->peer_ref) continue; - if (!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) { + + delete_ref = is_null_sha1(ref->peer_ref->new_sha1); + if (delete_ref && !allow_deleting_refs) { + error("remote does not support deleting refs"); + ret = -2; + continue; + } + if (!delete_ref && + !hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) { if (verbose) fprintf(stderr, "'%s': up-to-date\n", ref->name); continue; @@ -321,9 +334,13 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) * * (3) if both new and old are commit-ish, and new is a * descendant of old, it is OK. + * + * (4) regardless of all of the above, removing :B is + * always allowed. */ if (!force_update && + !delete_ref && !is_zero_sha1(ref->old_sha1) && !ref->force) { if (!has_sha1_file(ref->old_sha1) || @@ -347,12 +364,8 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) } } hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); - if (is_zero_sha1(ref->new_sha1)) { - error("cannot happen anymore"); - ret = -3; - continue; - } - new_refs++; + if (!delete_ref) + new_refs++; strcpy(old_hex, sha1_to_hex(ref->old_sha1)); new_hex = sha1_to_hex(ref->new_sha1); @@ -366,10 +379,16 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) else packet_write(out, "%s %s %s", old_hex, new_hex, ref->name); - fprintf(stderr, "updating '%s'", ref->name); - if (strcmp(ref->name, ref->peer_ref->name)) - fprintf(stderr, " using '%s'", ref->peer_ref->name); - fprintf(stderr, "\n from %s\n to %s\n", old_hex, new_hex); + if (delete_ref) + fprintf(stderr, "deleting '%s'\n", ref->name); + else { + fprintf(stderr, "updating '%s'", ref->name); + if (strcmp(ref->name, ref->peer_ref->name)) + fprintf(stderr, " using '%s'", + ref->peer_ref->name); + fprintf(stderr, "\n from %s\n to %s\n", + old_hex, new_hex); + } } packet_flush(out); @@ -387,6 +406,25 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) return ret; } +static void verify_remote_names(int nr_heads, char **heads) +{ + int i; + + for (i = 0; i < nr_heads; i++) { + const char *remote = strchr(heads[i], ':'); + + remote = remote ? (remote + 1) : heads[i]; + switch (check_ref_format(remote)) { + case 0: /* ok */ + case -2: /* ok but a single level -- that is fine for + * a match pattern. + */ + continue; + } + die("remote part of refspec is not a valid name in %s", + heads[i]); + } +} int main(int argc, char **argv) { @@ -438,6 +476,8 @@ int main(int argc, char **argv) usage(send_pack_usage); if (heads && send_all) usage(send_pack_usage); + verify_remote_names(nr_heads, heads); + pid = git_connect(fd, dest, exec); if (pid < 0) return 1; diff --git a/sha1_file.c b/sha1_file.c index 09456d23f8..1c4df5b73e 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1013,7 +1013,7 @@ void packed_object_info_detail(struct packed_git *p, for (;;) { switch (kind) { default: - die("corrupted pack file %s containing object of kind %d", + die("pack %s contains unknown object type %d", p->pack_name, kind); case OBJ_COMMIT: case OBJ_TREE: @@ -1063,7 +1063,7 @@ static int packed_object_info(struct packed_git *p, unsigned long offset, strcpy(type, type_names[kind]); break; default: - die("corrupted pack file %s containing object of kind %d", + die("pack %s contains unknown object type %d", p->pack_name, kind); } if (sizep) @@ -1261,7 +1261,7 @@ struct packed_git *find_sha1_pack(const unsigned char *sha1, } -int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep) +static int sha1_loose_object_info(const unsigned char *sha1, char *type, unsigned long *sizep) { int status; unsigned long mapsize, size; @@ -1270,20 +1270,8 @@ int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep char hdr[128]; map = map_sha1_file(sha1, &mapsize); - if (!map) { - struct pack_entry e; - - if (!find_pack_entry(sha1, &e, NULL)) { - reprepare_packed_git(); - if (!find_pack_entry(sha1, &e, NULL)) - return error("unable to find %s", sha1_to_hex(sha1)); - } - if (use_packed_git(e.p)) - die("cannot map packed file"); - status = packed_object_info(e.p, e.offset, type, sizep); - unuse_packed_git(e.p); - return status; - } + if (!map) + return error("unable to find %s", sha1_to_hex(sha1)); if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0) status = error("unable to unpack %s header", sha1_to_hex(sha1)); @@ -1299,6 +1287,23 @@ int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep return status; } +int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep) +{ + int status; + struct pack_entry e; + + if (!find_pack_entry(sha1, &e, NULL)) { + reprepare_packed_git(); + if (!find_pack_entry(sha1, &e, NULL)) + return sha1_loose_object_info(sha1, type, sizep); + } + if (use_packed_git(e.p)) + die("cannot map packed file"); + status = packed_object_info(e.p, e.offset, type, sizep); + unuse_packed_git(e.p); + return status; +} + static void *read_packed_sha1(const unsigned char *sha1, char *type, unsigned long *size) { struct pack_entry e; diff --git a/ssh-upload.c b/ssh-upload.c index 20b15eab57..0b52ae15cb 100644 --- a/ssh-upload.c +++ b/ssh-upload.c @@ -12,8 +12,6 @@ #include "rsh.h" #include "refs.h" -#include <string.h> - static unsigned char local_version = 1; static unsigned char remote_version; @@ -1,7 +1,5 @@ -#include <stdio.h> -#include <stdlib.h> -#include "strbuf.h" #include "cache.h" +#include "strbuf.h" void strbuf_init(struct strbuf *sb) { sb->buf = NULL; diff --git a/t/Makefile b/t/Makefile index 89835093fb..250a19019c 100644 --- a/t/Makefile +++ b/t/Makefile @@ -13,10 +13,6 @@ SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh) TSVN = $(wildcard t91[0-9][0-9]-*.sh) -ifdef NO_PYTHON - GIT_TEST_OPTS += --no-python -endif - all: $(T) clean $(T): @@ -27,12 +23,9 @@ clean: # we can test NO_OPTIMIZE_COMMITS independently of LC_ALL full-svn-test: - $(MAKE) $(TSVN) GIT_SVN_NO_LIB=1 GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C - $(MAKE) $(TSVN) GIT_SVN_NO_LIB=0 GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C - $(MAKE) $(TSVN) GIT_SVN_NO_LIB=1 GIT_SVN_NO_OPTIMIZE_COMMITS=0 \ - LC_ALL=en_US.UTF-8 - $(MAKE) $(TSVN) GIT_SVN_NO_LIB=0 GIT_SVN_NO_OPTIMIZE_COMMITS=0 \ - LC_ALL=en_US.UTF-8 + $(MAKE) $(TSVN) GIT_SVN_DELTA_FETCH=1 \ + GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C + $(MAKE) $(TSVN) GIT_SVN_NO_OPTIMIZE_COMMITS=0 LC_ALL=en_US.UTF-8 .PHONY: $(T) clean .NOTPARALLEL: diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index 29a1e72c61..99ada71349 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh @@ -7,17 +7,18 @@ then exit fi -GIT_DIR=$PWD/.git -GIT_SVN_DIR=$GIT_DIR/svn/git-svn -SVN_TREE=$GIT_SVN_DIR/svn-tree - -perl -e 'use SVN::Core' >/dev/null 2>&1 +perl -e 'use SVN::Core; $SVN::Core::VERSION gt "1.1.0" or die' >/dev/null 2>&1 if test $? -ne 0 then - echo 'Perl SVN libraries not found, tests requiring those will be skipped' - GIT_SVN_NO_LIB=1 + test_expect_success 'Perl SVN libraries not found, skipping test' : + test_done + exit fi +GIT_DIR=$PWD/.git +GIT_SVN_DIR=$GIT_DIR/svn/git-svn +SVN_TREE=$GIT_SVN_DIR/svn-tree + svnadmin >/dev/null 2>&1 if test $? -ne 1 then @@ -45,6 +46,6 @@ else svnadmin create "$svnrepo" fi -svnrepo="file://$svnrepo/test-git-svn" +svnrepo="file://$svnrepo" diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 6aff0b808c..0cd1c41866 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -19,11 +19,7 @@ modification *should* take notice and update the test vectors here. ' ################################################################ -# It appears that people are getting bitten by not installing -# 'merge' (usually part of RCS package in binary distributions) -# or have too old python without subprocess. Check them and error -# out before running any tests. Also catch the bogosity of trying -# to run tests without building while we are at it. +# It appears that people try to run tests without building... ../git >/dev/null if test $? != 1 @@ -32,22 +28,8 @@ then exit 1 fi -merge >/dev/null 2>/dev/null -if test $? = 127 -then - echo >&2 'You do not seem to have "merge" installed. -Please check INSTALL document.' - exit 1 -fi - . ./test-lib.sh -test "$no_python" || "$PYTHON" -c 'import subprocess' || { - echo >&2 'Your python seem to lack "subprocess" module. -Please check INSTALL document.' - exit 1 -} - ################################################################ # init-db has been done in an empty repository. # make sure it is empty. @@ -290,4 +272,13 @@ test_expect_success \ wc -l) && test $numparent = 1' +test_expect_success 'update-index D/F conflict' ' + mv path0 tmp && + mv path2 path0 && + mv tmp path2 && + git update-index --add --replace path2 path0/file2 && + numpath0=$(git ls-files path0 | wc -l) && + test $numpath0 = 1 +' + test_done diff --git a/t/t1004-read-tree-m-u-wf.sh b/t/t1004-read-tree-m-u-wf.sh index 018fbea450..4f664f6adf 100755 --- a/t/t1004-read-tree-m-u-wf.sh +++ b/t/t1004-read-tree-m-u-wf.sh @@ -8,23 +8,27 @@ test_description='read-tree -m -u checks working tree files' test_expect_success 'two-way setup' ' + mkdir subdir && echo >file1 file one && echo >file2 file two && - git update-index --add file1 file2 && + echo >subdir/file1 file one in subdirectory && + echo >subdir/file2 file two in subdirectory && + git update-index --add file1 file2 subdir/file1 subdir/file2 && git commit -m initial && git branch side && git tag -f branch-point && echo file2 is not tracked on the master anymore && - rm -f file2 && - git update-index --remove file2 && - git commit -a -m "master removes file2" + rm -f file2 subdir/file2 && + git update-index --remove file2 subdir/file2 && + git commit -a -m "master removes file2 and subdir/file2" ' test_expect_success 'two-way not clobbering' ' echo >file2 master creates untracked file2 && + echo >subdir/file2 master creates untracked subdir/file2 && if err=`git read-tree -m -u master side 2>&1` then echo should have complained @@ -34,20 +38,82 @@ test_expect_success 'two-way not clobbering' ' fi ' +echo file2 >.gitignore + +test_expect_success 'two-way with incorrect --exclude-per-directory (1)' ' + + if err=`git read-tree -m --exclude-per-directory=.gitignore master side 2>&1` + then + echo should have complained + false + else + echo "happy to see $err" + fi +' + +test_expect_success 'two-way with incorrect --exclude-per-directory (2)' ' + + if err=`git read-tree -m -u --exclude-per-directory=foo --exclude-per-directory=.gitignore master side 2>&1` + then + echo should have complained + false + else + echo "happy to see $err" + fi +' + +test_expect_success 'two-way clobbering a ignored file' ' + + git read-tree -m -u --exclude-per-directory=.gitignore master side +' + +rm -f .gitignore + # three-tree test -test_expect_success 'three-way not complaining' ' +test_expect_success 'three-way not complaining on an untracked path in both' ' - rm -f file2 && + rm -f file2 subdir/file2 && git checkout side && echo >file3 file three && - git update-index --add file3 && - git commit -a -m "side adds file3" && + echo >subdir/file3 file three && + git update-index --add file3 subdir/file3 && + git commit -a -m "side adds file3 and removes file2" && git checkout master && echo >file2 file two is untracked on the master side && + echo >subdir/file2 file two is untracked on the master side && git-read-tree -m -u branch-point master side ' +test_expect_success 'three-way not cloberring a working tree file' ' + + git reset --hard && + rm -f file2 subdir/file2 file3 subdir/file3 && + git checkout master && + echo >file3 file three created in master, untracked && + echo >subdir/file3 file three created in master, untracked && + if err=`git read-tree -m -u branch-point master side 2>&1` + then + echo should have complained + false + else + echo "happy to see $err" + fi +' + +echo >.gitignore file3 + +test_expect_success 'three-way not complaining on an untracked file' ' + + git reset --hard && + rm -f file2 subdir/file2 file3 subdir/file3 && + git checkout master && + echo >file3 file three created in master, untracked && + echo >subdir/file3 file three created in master, untracked && + + git read-tree -m -u --exclude-per-directory=.gitignore branch-point master side +' + test_done diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh index 0272dd4293..eebe643bda 100755 --- a/t/t1200-tutorial.sh +++ b/t/t1200-tutorial.sh @@ -37,8 +37,6 @@ test_expect_success 'tree' "test 8988da15d077d4829fc51d8544c097def6644dbb = $tre output="$(echo "Initial commit" | git-commit-tree $(git-write-tree) 2>&1 > .git/refs/heads/master)" -test_expect_success 'commit' "test 'Committing initial tree 8988da15d077d4829fc51d8544c097def6644dbb' = \"$output\"" - git-diff-index -p HEAD > diff.output test_expect_success 'git-diff-index -p HEAD' 'cmp diff.expect diff.output' diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index 0de2497746..e48a4ecdcf 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -265,6 +265,16 @@ EOF test_expect_success '--get-regexp' \ 'git-repo-config --get-regexp in > output && cmp output expect' +git-repo-config --add nextsection.nonewline "wow4 for you" + +cat > expect << EOF +wow2 for me +wow4 for you +EOF + +test_expect_success '--add' \ + 'git-repo-config --get-all nextsection.nonewline > output && cmp output expect' + cat > .git/config << EOF [novalue] variable @@ -333,5 +343,53 @@ EOF test_expect_success '--set in alternative GIT_CONFIG' 'cmp other-config expect' +cat > .git/config << EOF +# Hallo + #Bello +[branch "eins"] + x = 1 +[branch.eins] + y = 1 + [branch "1 234 blabl/a"] +weird +EOF + +test_expect_success "rename section" \ + "git-repo-config --rename-section branch.eins branch.zwei" + +cat > expect << EOF +# Hallo + #Bello +[branch "zwei"] + x = 1 +[branch "zwei"] + y = 1 + [branch "1 234 blabl/a"] +weird +EOF + +test_expect_success "rename succeeded" "diff -u expect .git/config" + +test_expect_failure "rename non-existing section" \ + 'git-repo-config --rename-section branch."world domination" branch.drei' + +test_expect_success "rename succeeded" "diff -u expect .git/config" + +test_expect_success "rename another section" \ + 'git-repo-config --rename-section branch."1 234 blabl/a" branch.drei' + +cat > expect << EOF +# Hallo + #Bello +[branch "zwei"] + x = 1 +[branch "zwei"] + y = 1 +[branch "drei"] +weird +EOF + +test_expect_success "rename succeeded" "diff -u expect .git/config" + test_done diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index 6a917f2ff4..5637cb5eac 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -63,8 +63,8 @@ test_expect_failure \ "test $B"' = $(cat .git/'"$m"')' rm -f .git/$m -mkdir -p .git/logs/refs/heads -touch .git/logs/refs/heads/master +: a repository with working tree always has reflog these days... +: >.git/logs/refs/heads/master test_expect_success \ "create $m (logged by touch)" \ 'GIT_COMMITTER_DATE="2005-05-26 23:30" \ diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index acb54b6a07..a6ea0f6a19 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -70,4 +70,51 @@ test_expect_success \ git-branch -d l/m && git-branch l' +test_expect_success \ + 'git branch -m m m/m should work' \ + 'git-branch -l m && + git-branch -m m m/m && + test -f .git/logs/refs/heads/m/m' + +test_expect_success \ + 'git branch -m n/n n should work' \ + 'git-branch -l n/n && + git-branch -m n/n n + test -f .git/logs/refs/heads/n' + +test_expect_failure \ + 'git branch -m o/o o should fail when o/p exists' \ + 'git-branch o/o && + git-branch o/p && + git-branch -m o/o o' + +test_expect_failure \ + 'git branch -m q r/q should fail when r exists' \ + 'git-branch q && + git-branch r && + git-branch -m q r/q' + +git-repo-config branch.s/s.dummy Hello + +test_expect_success \ + 'git branch -m s/s s should work when s/t is deleted' \ + 'git-branch -l s/s && + test -f .git/logs/refs/heads/s/s && + git-branch -l s/t && + test -f .git/logs/refs/heads/s/t && + git-branch -d s/t && + git-branch -m s/s s && + test -f .git/logs/refs/heads/s' + +test_expect_success 'config information was renamed, too' \ + "test $(git-repo-config branch.s.dummy) = Hello && + ! git-repo-config branch.s/s/dummy" + +test_expect_failure \ + 'git-branch -m u v should fail when the reflog for u is a symlink' \ + 'git-branch -l u && + mv .git/logs/refs/heads/u real-u && + ln -s real-u .git/logs/refs/heads/u && + git-branch -m u v' + test_done diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh index 201d1642da..e31cf93a00 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh @@ -43,19 +43,19 @@ test_expect_success \ test_expect_success \ 'Test that git-rm foo succeeds' \ - 'git-rm foo' + 'git-rm --cached foo' test_expect_success \ 'Post-check that foo exists but is not in index after git-rm foo' \ '[ -f foo ] && ! git-ls-files --error-unmatch foo' test_expect_success \ - 'Pre-check that bar exists and is in index before "git-rm -f bar"' \ + 'Pre-check that bar exists and is in index before "git-rm bar"' \ '[ -f bar ] && git-ls-files --error-unmatch bar' test_expect_success \ - 'Test that "git-rm -f bar" succeeds' \ - 'git-rm -f bar' + 'Test that "git-rm bar" succeeds' \ + 'git-rm bar' test_expect_success \ 'Post-check that bar does not exist and is not in index after "git-rm -f bar"' \ @@ -84,4 +84,74 @@ test_expect_success \ 'When the rm in "git-rm -f" fails, it should not remove the file from the index' \ 'git-ls-files --error-unmatch baz' +# Now, failure cases. +test_expect_success 'Re-add foo and baz' ' + git add foo baz && + git ls-files --error-unmatch foo baz +' + +test_expect_success 'Modify foo -- rm should refuse' ' + echo >>foo && + ! git rm foo baz && + test -f foo && + test -f baz && + git ls-files --error-unmatch foo baz +' + +test_expect_success 'Modified foo -- rm -f should work' ' + git rm -f foo baz && + test ! -f foo && + test ! -f baz && + ! git ls-files --error-unmatch foo && + ! git ls-files --error-unmatch bar +' + +test_expect_success 'Re-add foo and baz for HEAD tests' ' + echo frotz >foo && + git checkout HEAD -- baz && + git add foo baz && + git ls-files --error-unmatch foo baz +' + +test_expect_success 'foo is different in index from HEAD -- rm should refuse' ' + ! git rm foo baz && + test -f foo && + test -f baz && + git ls-files --error-unmatch foo baz +' + +test_expect_success 'but with -f it should work.' ' + git rm -f foo baz && + test ! -f foo && + test ! -f baz && + ! git ls-files --error-unmatch foo + ! git ls-files --error-unmatch baz +' + +test_expect_success 'Recursive test setup' ' + mkdir -p frotz && + echo qfwfq >frotz/nitfol && + git add frotz && + git commit -m "subdir test" +' + +test_expect_success 'Recursive without -r fails' ' + ! git rm frotz && + test -d frotz && + test -f frotz/nitfol +' + +test_expect_success 'Recursive with -r but dirty' ' + echo qfwfq >>frotz/nitfol + ! git rm -r frotz && + test -d frotz && + test -f frotz/nitfol +' + +test_expect_success 'Recursive with -r -f' ' + git rm -f -r frotz && + ! test -f frotz/nitfol && + ! test -d frotz +' + test_done diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index 1bc5b7a412..adf4993bac 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -109,12 +109,10 @@ index d99af23..8b32fb5 100644 + whitespace at beginning whitespace change -whitespace in the middle --whitespace at end +white space in the middle -+whitespace at end + whitespace at end unchanged line --CR at endQ -+CR at end + CR at endQ EOF git-diff -b > out test_expect_success 'another test, with -b' 'diff -u expect out' diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh new file mode 100755 index 0000000000..5ee5b23095 --- /dev/null +++ b/t/t4200-rerere.sh @@ -0,0 +1,154 @@ +#!/bin/sh +# +# Copyright (c) 2006 Johannes E. Schindelin +# + +test_description='git-rerere +' + +. ./test-lib.sh + +cat > a1 << EOF +Whether 'tis nobler in the mind to suffer +The slings and arrows of outrageous fortune, +Or to take arms against a sea of troubles, +And by opposing end them? To die: to sleep; +No more; and by a sleep to say we end +The heart-ache and the thousand natural shocks +That flesh is heir to, 'tis a consummation +Devoutly to be wish'd. +EOF + +git add a1 +git commit -q -a -m initial + +git checkout -b first +cat >> a1 << EOF +To die, to sleep; +To sleep: perchance to dream: ay, there's the rub; +For in that sleep of death what dreams may come +When we have shuffled off this mortal coil, +Must give us pause: there's the respect +That makes calamity of so long life; +EOF +git commit -q -a -m first + +git checkout -b second master +git show first:a1 | sed 's/To die, t/To die! T/' > a1 +git commit -q -a -m second + +# activate rerere +mkdir .git/rr-cache + +test_expect_failure 'conflicting merge' 'git pull . first' + +sha1=4f58849a60b4f969a2848966b6d02893b783e8fb +rr=.git/rr-cache/$sha1 +test_expect_success 'recorded preimage' "grep ======= $rr/preimage" + +test_expect_success 'no postimage or thisimage yet' \ + "test ! -f $rr/postimage -a ! -f $rr/thisimage" + +git show first:a1 > a1 + +cat > expect << EOF +--- a/a1 ++++ b/a1 +@@ -6,11 +6,7 @@ + The heart-ache and the thousand natural shocks + That flesh is heir to, 'tis a consummation + Devoutly to be wish'd. +-<<<<<<< +-To die! To sleep; +-======= + To die, to sleep; +->>>>>>> + To sleep: perchance to dream: ay, there's the rub; + For in that sleep of death what dreams may come + When we have shuffled off this mortal coil, +EOF + +git rerere diff > out + +test_expect_success 'rerere diff' 'diff -u expect out' + +cat > expect << EOF +a1 +EOF + +git rerere status > out + +test_expect_success 'rerere status' 'diff -u expect out' + +test_expect_success 'commit succeeds' \ + "git commit -q -a -m 'prefer first over second'" + +test_expect_success 'recorded postimage' "test -f $rr/postimage" + +git checkout -b third master +git show second^:a1 | sed 's/To die: t/To die! T/' > a1 +git commit -q -a -m third + +test_expect_failure 'another conflicting merge' 'git pull . first' + +git show first:a1 | sed 's/To die: t/To die! T/' > expect +test_expect_success 'rerere kicked in' "! grep ======= a1" + +test_expect_success 'rerere prefers first change' 'diff -u a1 expect' + +rm $rr/postimage +echo "$sha1 a1" | tr '\012' '\0' > .git/rr-cache/MERGE_RR + +test_expect_success 'rerere clear' 'git rerere clear' + +test_expect_success 'clear removed the directory' "test ! -d $rr" + +mkdir $rr +echo Hello > $rr/preimage +echo World > $rr/postimage + +sha2=4000000000000000000000000000000000000000 +rr2=.git/rr-cache/$sha2 +mkdir $rr2 +echo Hello > $rr2/preimage + +case "$(date -d @11111111 +%s 2>/dev/null)" in +[1-9]*) + # it is a recent GNU date. good. + now=$(date +%s) + almost_15_days_ago=$(($now+60-15*86400)) + just_over_15_days_ago=$(($now-1-15*86400)) + almost_60_days_ago=$(($now+60-60*86400)) + just_over_60_days_ago=$(($now-1-60*86400)) + predate1="$(date -d "@$almost_60_days_ago" +%c)" + predate2="$(date -d "@$almost_15_days_ago" +%c)" + postdate1="$(date -d "@$just_over_60_days_ago" +%c)" + postdate2="$(date -d "@$just_over_15_days_ago" +%c)" + ;; +*) + # it is not GNU date. oh, well. + predate1="$(date)" + predate2="$(date)" + postdate1='1 Oct 2006 00:00:00' + postdate2='1 Dec 2006 00:00:00' +esac + +touch -m -d "$predate1" $rr/preimage +touch -m -d "$predate2" $rr2/preimage + +test_expect_success 'garbage collection (part1)' 'git rerere gc' + +test_expect_success 'young records still live' \ + "test -f $rr/preimage -a -f $rr2/preimage" + +touch -m -d "$postdate1" $rr/preimage +touch -m -d "$postdate2" $rr2/preimage + +test_expect_success 'garbage collection (part2)' 'git rerere gc' + +test_expect_success 'old records rest in peace' \ + "test ! -f $rr/preimage -a ! -f $rr2/preimage" + +test_done + + diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index 8afb899717..28744b35e1 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -64,6 +64,16 @@ test_expect_success \ cmp victim/.git/refs/heads/master .git/refs/heads/master ' +test_expect_success \ + 'push can be used to delete a ref' ' + cd victim && + git branch extra master && + cd .. && + test -f victim/.git/refs/heads/extra && + git-send-pack ./victim/.git/ :extra master && + ! test -f victim/.git/refs/heads/extra +' + unset GIT_CONFIG GIT_CONFIG_LOCAL HOME=`pwd`/no-such-directory export HOME ;# this way we force the victim/.git/config to be used. diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index fa10840303..77c3c575d8 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -135,8 +135,13 @@ test_expect_success "clone shallow" "git-clone --depth 2 . shallow" test_expect_success "clone shallow object count" \ "test \"in-pack: 18\" = \"$(grep in-pack count.shallow)\"" -test_expect_success "clone shallow object count (part 2)" \ - "test -z \"$(grep -v in-pack count.shallow | sed "s/^.*: 0//")\"" +count_output () { + sed -e '/^in-pack:/d' -e '/^packs:/d' -e '/: 0$/d' "$1" +} + +test_expect_success "clone shallow object count (part 2)" ' + test -z "$(count_output count.shallow)" +' test_expect_success "fsck in shallow repo" \ "(cd shallow; git-fsck-objects --full)" diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index a11ab0ad41..90eeeba2a3 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -23,15 +23,14 @@ test_expect_success "clone and setup child repos" ' git clone . two && cd two && git repo-config branch.master.remote one && - { - echo "URL: ../one/.git/" - echo "Pull: refs/heads/master:refs/heads/one" - } >.git/remotes/one + git repo-config remote.one.url ../one/.git/ && + git repo-config remote.one.fetch refs/heads/master:refs/heads/one && cd .. && git clone . three && cd three && git repo-config branch.master.remote two && git repo-config branch.master.merge refs/heads/one && + mkdir -p .git/remotes && { echo "URL: ../two/.git/" echo "Pull: refs/heads/master:refs/heads/two" diff --git a/t/t6005-rev-list-count.sh b/t/t6005-rev-list-count.sh new file mode 100755 index 0000000000..334fccf58c --- /dev/null +++ b/t/t6005-rev-list-count.sh @@ -0,0 +1,51 @@ +#!/bin/sh + +test_description='git-rev-list --max-count and --skip test' + +. ./test-lib.sh + +test_expect_success 'setup' ' + for n in 1 2 3 4 5 ; do \ + echo $n > a ; \ + git add a ; \ + git commit -m "$n" ; \ + done +' + +test_expect_success 'no options' ' + test $(git-rev-list HEAD | wc -l) = 5 +' + +test_expect_success '--max-count' ' + test $(git-rev-list HEAD --max-count=0 | wc -l) = 0 && + test $(git-rev-list HEAD --max-count=3 | wc -l) = 3 && + test $(git-rev-list HEAD --max-count=5 | wc -l) = 5 && + test $(git-rev-list HEAD --max-count=10 | wc -l) = 5 +' + +test_expect_success '--max-count all forms' ' + test $(git-rev-list HEAD --max-count=1 | wc -l) = 1 && + test $(git-rev-list HEAD -1 | wc -l) = 1 && + test $(git-rev-list HEAD -n1 | wc -l) = 1 && + test $(git-rev-list HEAD -n 1 | wc -l) = 1 +' + +test_expect_success '--skip' ' + test $(git-rev-list HEAD --skip=0 | wc -l) = 5 && + test $(git-rev-list HEAD --skip=3 | wc -l) = 2 && + test $(git-rev-list HEAD --skip=5 | wc -l) = 0 && + test $(git-rev-list HEAD --skip=10 | wc -l) = 0 +' + +test_expect_success '--skip --max-count' ' + test $(git-rev-list HEAD --skip=0 --max-count=0 | wc -l) = 0 && + test $(git-rev-list HEAD --skip=0 --max-count=10 | wc -l) = 5 && + test $(git-rev-list HEAD --skip=3 --max-count=0 | wc -l) = 0 && + test $(git-rev-list HEAD --skip=3 --max-count=1 | wc -l) = 1 && + test $(git-rev-list HEAD --skip=3 --max-count=2 | wc -l) = 2 && + test $(git-rev-list HEAD --skip=3 --max-count=10 | wc -l) = 2 && + test $(git-rev-list HEAD --skip=5 --max-count=10 | wc -l) = 0 && + test $(git-rev-list HEAD --skip=10 --max-count=10 | wc -l) = 0 +' + +test_done diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh new file mode 100644 index 0000000000..5d9b6f34b8 --- /dev/null +++ b/t/t6023-merge-file.sh @@ -0,0 +1,116 @@ +#!/bin/sh + +test_description='RCS merge replacement: merge-file' +. ./test-lib.sh + +cat > orig.txt << EOF +Dominus regit me, +et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +EOF + +cat > new1.txt << EOF +Dominus regit me, +et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +Nam et si ambulavero in medio umbrae mortis, +non timebo mala, quoniam tu mecum es: +virga tua et baculus tuus ipsa me consolata sunt. +EOF + +cat > new2.txt << EOF +Dominus regit me, et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +EOF + +cat > new3.txt << EOF +DOMINUS regit me, +et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +EOF + +cat > new4.txt << EOF +Dominus regit me, et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +EOF +echo -n "propter nomen suum." >> new4.txt + +cp new1.txt test.txt +test_expect_success "merge without conflict" \ + "git-merge-file test.txt orig.txt new2.txt" + +cp new1.txt test2.txt +test_expect_success "merge without conflict (missing LF at EOF)" \ + "git-merge-file test2.txt orig.txt new2.txt" + +test_expect_success "merge result added missing LF" \ + "diff -u test.txt test2.txt" + +cp test.txt backup.txt +test_expect_failure "merge with conflicts" \ + "git-merge-file test.txt orig.txt new3.txt" + +cat > expect.txt << EOF +<<<<<<< test.txt +Dominus regit me, et nihil mihi deerit. +======= +DOMINUS regit me, +et nihil mihi deerit. +>>>>>>> new3.txt +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +Nam et si ambulavero in medio umbrae mortis, +non timebo mala, quoniam tu mecum es: +virga tua et baculus tuus ipsa me consolata sunt. +EOF + +test_expect_success "expected conflict markers" "diff -u test.txt expect.txt" + +cp backup.txt test.txt +test_expect_failure "merge with conflicts, using -L" \ + "git-merge-file -L 1 -L 2 test.txt orig.txt new3.txt" + +cat > expect.txt << EOF +<<<<<<< 1 +Dominus regit me, et nihil mihi deerit. +======= +DOMINUS regit me, +et nihil mihi deerit. +>>>>>>> new3.txt +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +Nam et si ambulavero in medio umbrae mortis, +non timebo mala, quoniam tu mecum es: +virga tua et baculus tuus ipsa me consolata sunt. +EOF + +test_expect_success "expected conflict markers, with -L" \ + "diff -u test.txt expect.txt" + +test_done + diff --git a/t/t6023-merge-rename-nocruft.sh b/t/t6023-merge-rename-nocruft.sh new file mode 100755 index 0000000000..69c66cf6fa --- /dev/null +++ b/t/t6023-merge-rename-nocruft.sh @@ -0,0 +1,97 @@ +#!/bin/sh + +test_description='Merge-recursive merging renames' +. ./test-lib.sh + +test_expect_success setup \ +' +cat >A <<\EOF && +a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +b bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +c cccccccccccccccccccccccccccccccccccccccccccccccc +d dddddddddddddddddddddddddddddddddddddddddddddddd +e eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +f ffffffffffffffffffffffffffffffffffffffffffffffff +g gggggggggggggggggggggggggggggggggggggggggggggggg +h hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh +i iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii +j jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj +k kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk +l llllllllllllllllllllllllllllllllllllllllllllllll +m mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm +n nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn +o oooooooooooooooooooooooooooooooooooooooooooooooo +EOF + +cat >M <<\EOF && +A AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +B BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB +C CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC +D DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD +E EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +F FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +G GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG +H HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +I IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII +J JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ +K KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK +L LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL +M MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM +N NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +O OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO +EOF + +git add A M && +git commit -m "initial has A and M" && +git branch white && +git branch red && + +git checkout white && +sed -e "/^g /s/.*/g : white changes a line/" <A >B && +sed -e "/^G /s/.*/G : colored branch changes a line/" <M >N && +rm -f A M && +git update-index --add --remove A B M N && +git commit -m "white renames A->B, M->N" && + +git checkout red && +echo created by red >R && +git update-index --add R && +git commit -m "red creates R" && + +git checkout master' + +# This test broke in 65ac6e9c3f47807cb603af07a6a9e1a43bc119ae +test_expect_success 'merge white into red (A->B,M->N)' \ +' + git checkout -b red-white red && + git merge white && + git write-tree >/dev/null || { + echo "BAD: merge did not complete" + return 1 + } + + test -f B || { + echo "BAD: B does not exist in working directory" + return 1 + } + test -f N || { + echo "BAD: N does not exist in working directory" + return 1 + } + test -f R || { + echo "BAD: R does not exist in working directory" + return 1 + } + + test -f A && { + echo "BAD: A still exists in working directory" + return 1 + } + test -f M && { + echo "BAD: M still exists in working directory" + return 1 + } + return 0 +' + +test_done diff --git a/t/t6024-recursive-merge.sh b/t/t6024-recursive-merge.sh new file mode 100644 index 0000000000..69b18f7d81 --- /dev/null +++ b/t/t6024-recursive-merge.sh @@ -0,0 +1,80 @@ +#!/bin/sh + +test_description='Test merge without common ancestors' +. ./test-lib.sh + +# This scenario is based on a real-world repository of Shawn Pearce. + +# 1 - A - D - F +# \ X / +# B X +# X \ +# 2 - C - E - G + +export GIT_COMMITTER_DATE="2006-12-12 23:28:00 +0100" +echo 1 > a1 +git add a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:00" git commit -m 1 a1 + +git checkout -b A master +echo A > a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:01" git commit -m A a1 + +git checkout -b B master +echo B > a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:02" git commit -m B a1 + +git checkout -b D A +git-rev-parse B > .git/MERGE_HEAD +echo D > a1 +git update-index a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:03" git commit -m D + +git symbolic-ref HEAD refs/heads/other +echo 2 > a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:04" git commit -m 2 a1 + +git checkout -b C +echo C > a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:05" git commit -m C a1 + +git checkout -b E C +git-rev-parse B > .git/MERGE_HEAD +echo E > a1 +git update-index a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:06" git commit -m E + +git checkout -b G E +git-rev-parse A > .git/MERGE_HEAD +echo G > a1 +git update-index a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:07" git commit -m G + +git checkout -b F D +git-rev-parse C > .git/MERGE_HEAD +echo F > a1 +git update-index a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F + +test_expect_failure "combined merge conflicts" "git merge -m final G" + +cat > expect << EOF +<<<<<<< HEAD:a1 +F +======= +G +>>>>>>> G:a1 +EOF + +test_expect_success "result contains a conflict" "diff -u expect a1" + +git ls-files --stage > out +cat > expect << EOF +100644 da056ce14a2241509897fa68bb2b3b6e6194ef9e 1 a1 +100644 cf84443e49e1b366fac938711ddf4be2d4d1d9e9 2 a1 +100644 fd7923529855d0b274795ae3349c5e0438333979 3 a1 +EOF + +test_expect_success "virtual trees were processed" "diff -u expect out" + +test_done diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh index 23a1eff3bb..2f4ff82e14 100755 --- a/t/t7001-mv.sh +++ b/t/t7001-mv.sh @@ -105,4 +105,17 @@ test_expect_success "Michael Cassar's test case" ' } ' +rm -fr papers partA path? + +test_expect_success "Sergey Vlasov's test case" ' + rm -fr .git && + git init-db && + mkdir ab && + date >ab.c && + date >ab/d && + git add ab.c ab && + git commit -m 'initial' && + git mv ab a +' + test_done diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index 34a3ccd31c..c22fe47213 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -19,180 +19,176 @@ esac echo 'define NO_SVN_TESTS to skip git-svn tests' -mkdir import -cd import - -echo foo > foo -if test -z "$NO_SYMLINK" -then - ln -s foo foo.link -fi -mkdir -p dir/a/b/c/d/e -echo 'deep dir' > dir/a/b/c/d/e/file -mkdir -p bar -echo 'zzz' > bar/zzz -echo '#!/bin/sh' > exec.sh -chmod +x exec.sh -svn import -m 'import for git-svn' . "$svnrepo" >/dev/null - -cd .. -rm -rf import - test_expect_success \ - 'initialize git-svn' \ - "git-svn init $svnrepo" + 'initialize git-svn' " + mkdir import && + cd import && + echo foo > foo && + if test -z '$NO_SYMLINK' + then + ln -s foo foo.link + fi + mkdir -p dir/a/b/c/d/e && + echo 'deep dir' > dir/a/b/c/d/e/file && + mkdir bar && + echo 'zzz' > bar/zzz && + echo '#!/bin/sh' > exec.sh && + chmod +x exec.sh && + svn import -m 'import for git-svn' . $svnrepo >/dev/null && + cd .. && + rm -rf import && + git-svn init $svnrepo" test_expect_success \ 'import an SVN revision into git' \ 'git-svn fetch' -test_expect_success "checkout from svn" "svn co $svnrepo $SVN_TREE" +test_expect_success "checkout from svn" "svn co $svnrepo '$SVN_TREE'" name='try a deep --rmdir with a commit' -git checkout -f -b mybranch remotes/git-svn -mv dir/a/b/c/d/e/file dir/file -cp dir/file file -git update-index --add --remove dir/a/b/c/d/e/file dir/file file -git commit -m "$name" - -test_expect_success "$name" \ - "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch && - svn up $SVN_TREE && - test -d $SVN_TREE/dir && test ! -d $SVN_TREE/dir/a" +test_expect_success "$name" " + git checkout -f -b mybranch remotes/git-svn && + mv dir/a/b/c/d/e/file dir/file && + cp dir/file file && + git update-index --add --remove dir/a/b/c/d/e/file dir/file file && + git commit -m '$name' && + git-svn set-tree --find-copies-harder --rmdir \ + remotes/git-svn..mybranch && + svn up '$SVN_TREE' && + test -d '$SVN_TREE'/dir && test ! -d '$SVN_TREE'/dir/a" name='detect node change from file to directory #1' -mkdir dir/new_file -mv dir/file dir/new_file/file -mv dir/new_file dir/file -git update-index --remove dir/file -git update-index --add dir/file/file -git commit -m "$name" - -test_expect_failure "$name" \ - 'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch' \ - || true +test_expect_failure "$name" " + mkdir dir/new_file && + mv dir/file dir/new_file/file && + mv dir/new_file dir/file && + git update-index --remove dir/file && + git update-index --add dir/file/file && + git commit -m '$name' && + git-svn set-tree --find-copies-harder --rmdir \ + remotes/git-svn..mybranch" || true name='detect node change from directory to file #1' -rm -rf dir $GIT_DIR/index -git checkout -f -b mybranch2 remotes/git-svn -mv bar/zzz zzz -rm -rf bar -mv zzz bar -git update-index --remove -- bar/zzz -git update-index --add -- bar -git commit -m "$name" - -test_expect_failure "$name" \ - 'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch2' \ - || true +test_expect_failure "$name" " + rm -rf dir '$GIT_DIR'/index && + git checkout -f -b mybranch2 remotes/git-svn && + mv bar/zzz zzz && + rm -rf bar && + mv zzz bar && + git update-index --remove -- bar/zzz && + git update-index --add -- bar && + git commit -m '$name' && + git-svn set-tree --find-copies-harder --rmdir \ + remotes/git-svn..mybranch2" || true name='detect node change from file to directory #2' -rm -f $GIT_DIR/index -git checkout -f -b mybranch3 remotes/git-svn -rm bar/zzz -git-update-index --remove bar/zzz -mkdir bar/zzz -echo yyy > bar/zzz/yyy -git-update-index --add bar/zzz/yyy -git commit -m "$name" - -test_expect_failure "$name" \ - 'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch3' \ - || true +test_expect_failure "$name" " + rm -f '$GIT_DIR'/index && + git checkout -f -b mybranch3 remotes/git-svn && + rm bar/zzz && + git-update-index --remove bar/zzz && + mkdir bar/zzz && + echo yyy > bar/zzz/yyy && + git-update-index --add bar/zzz/yyy && + git commit -m '$name' && + git-svn set-tree --find-copies-harder --rmdir \ + remotes/git-svn..mybranch3" || true name='detect node change from directory to file #2' -rm -f $GIT_DIR/index -git checkout -f -b mybranch4 remotes/git-svn -rm -rf dir -git update-index --remove -- dir/file -touch dir -echo asdf > dir -git update-index --add -- dir -git commit -m "$name" - -test_expect_failure "$name" \ - 'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch4' \ - || true +test_expect_failure "$name" " + rm -f '$GIT_DIR'/index && + git checkout -f -b mybranch4 remotes/git-svn && + rm -rf dir && + git update-index --remove -- dir/file && + touch dir && + echo asdf > dir && + git update-index --add -- dir && + git commit -m '$name' && + git-svn set-tree --find-copies-harder --rmdir \ + remotes/git-svn..mybranch4" || true name='remove executable bit from a file' -rm -f $GIT_DIR/index -git checkout -f -b mybranch5 remotes/git-svn -chmod -x exec.sh -git update-index exec.sh -git commit -m "$name" - -test_expect_success "$name" \ - "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 && - svn up $SVN_TREE && - test ! -x $SVN_TREE/exec.sh" +test_expect_success "$name" " + rm -f '$GIT_DIR'/index && + git checkout -f -b mybranch5 remotes/git-svn && + chmod -x exec.sh && + git update-index exec.sh && + git commit -m '$name' && + git-svn set-tree --find-copies-harder --rmdir \ + remotes/git-svn..mybranch5 && + svn up '$SVN_TREE' && + test ! -x '$SVN_TREE'/exec.sh" name='add executable bit back file' -chmod +x exec.sh -git update-index exec.sh -git commit -m "$name" - -test_expect_success "$name" \ - "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 && - svn up $SVN_TREE && - test -x $SVN_TREE/exec.sh" - +test_expect_success "$name" " + chmod +x exec.sh && + git update-index exec.sh && + git commit -m '$name' && + git-svn set-tree --find-copies-harder --rmdir \ + remotes/git-svn..mybranch5 && + svn up '$SVN_TREE' && + test -x '$SVN_TREE'/exec.sh" if test -z "$NO_SYMLINK" then name='executable file becomes a symlink to bar/zzz (file)' - rm exec.sh - ln -s bar/zzz exec.sh - git update-index exec.sh - git commit -m "$name" - test_expect_success "$name" \ - "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 && - svn up $SVN_TREE && - test -L $SVN_TREE/exec.sh" + test_expect_success "$name" " + rm exec.sh && + ln -s bar/zzz exec.sh && + git update-index exec.sh && + git commit -m '$name' && + git-svn set-tree --find-copies-harder --rmdir \ + remotes/git-svn..mybranch5 && + svn up '$SVN_TREE' && + test -L '$SVN_TREE'/exec.sh" name='new symlink is added to a file that was also just made executable' - chmod +x bar/zzz - ln -s bar/zzz exec-2.sh - git update-index --add bar/zzz exec-2.sh - git commit -m "$name" - test_expect_success "$name" \ - "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 && - svn up $SVN_TREE && - test -x $SVN_TREE/bar/zzz && - test -L $SVN_TREE/exec-2.sh" + test_expect_success "$name" " + chmod +x bar/zzz && + ln -s bar/zzz exec-2.sh && + git update-index --add bar/zzz exec-2.sh && + git commit -m '$name' && + git-svn set-tree --find-copies-harder --rmdir \ + remotes/git-svn..mybranch5 && + svn up '$SVN_TREE' && + test -x '$SVN_TREE'/bar/zzz && + test -L '$SVN_TREE'/exec-2.sh" name='modify a symlink to become a file' - echo git help > help || true - rm exec-2.sh - cp help exec-2.sh - git update-index exec-2.sh - git commit -m "$name" - - test_expect_success "$name" \ - "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 && - svn up $SVN_TREE && - test -f $SVN_TREE/exec-2.sh && - test ! -L $SVN_TREE/exec-2.sh && - diff -u help $SVN_TREE/exec-2.sh" + test_expect_success "$name" " + echo git help > help || true && + rm exec-2.sh && + cp help exec-2.sh && + git update-index exec-2.sh && + git commit -m '$name' && + git-svn set-tree --find-copies-harder --rmdir \ + remotes/git-svn..mybranch5 && + svn up '$SVN_TREE' && + test -f '$SVN_TREE'/exec-2.sh && + test ! -L '$SVN_TREE'/exec-2.sh && + diff -u help $SVN_TREE/exec-2.sh" fi if test "$have_utf8" = t then name="commit with UTF-8 message: locale: $GIT_SVN_LC_ALL" - echo '# hello' >> exec-2.sh - git update-index exec-2.sh - git commit -m 'éïâˆ' - export LC_ALL="$GIT_SVN_LC_ALL" - test_expect_success "$name" "git-svn commit HEAD" + LC_ALL="$GIT_SVN_LC_ALL" + export LC_ALL + test_expect_success "$name" " + echo '# hello' >> exec-2.sh && + git update-index exec-2.sh && + git commit -m 'éïâˆ' && + git-svn set-tree HEAD" unset LC_ALL else echo "UTF-8 locale not set, test skipped ($GIT_SVN_LC_ALL)" @@ -228,6 +224,9 @@ tree 56a30b966619b863674f5978696f4a3594f2fca9 tree d667270a1f7b109f5eb3aaea21ede14b56bfdd6e tree 8f51f74cf0163afc9ad68a4b1537288c4558b5a4 EOF + +echo tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 >> expected + test_expect_success "$name" "diff -u a expected" test_done diff --git a/t/t9101-git-svn-props.sh b/t/t9101-git-svn-props.sh index a5a235f100..5543b07f16 100755 --- a/t/t9101-git-svn-props.sh +++ b/t/t9101-git-svn-props.sh @@ -74,7 +74,7 @@ test_expect_success "$name" \ 'git checkout -b mybranch remotes/git-svn && echo Hi again >> kw.c && git commit -a -m "test keywoards ignoring" && - git-svn commit remotes/git-svn..mybranch && + git-svn set-tree remotes/git-svn..mybranch && git pull . remotes/git-svn' expect='/* $Id$ */' diff --git a/t/t9102-git-svn-deep-rmdir.sh b/t/t9102-git-svn-deep-rmdir.sh index d693d183c8..572aaedc06 100755 --- a/t/t9102-git-svn-deep-rmdir.sh +++ b/t/t9102-git-svn-deep-rmdir.sh @@ -21,7 +21,7 @@ test_expect_success 'mirror via git-svn' " test_expect_success 'Try a commit on rmdir' " git rm -f deeply/nested/directory/number/2/another && git commit -a -m 'remove another' && - git-svn commit --rmdir HEAD && + git-svn set-tree --rmdir HEAD && svn ls -R $svnrepo | grep ^deeply/nested/directory/number/1 " diff --git a/t/t9103-git-svn-graft-branches.sh b/t/t9103-git-svn-graft-branches.sh index cc62d4ece8..293b98f928 100755 --- a/t/t9103-git-svn-graft-branches.sh +++ b/t/t9103-git-svn-graft-branches.sh @@ -1,6 +1,8 @@ test_description='git-svn graft-branches' . ./lib-git-svn.sh +svnrepo="$svnrepo/test-git-svn" + test_expect_success 'initialize repo' " mkdir import && cd import && diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 01488ff78a..8d2e2fec39 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -6,13 +6,6 @@ test_description='git-svn --follow-parent fetching' . ./lib-git-svn.sh -if test -n "$GIT_SVN_NO_LIB" && test "$GIT_SVN_NO_LIB" -ne 0 -then - echo 'Skipping: --follow-parent needs SVN libraries' - test_done - exit 0 -fi - test_expect_success 'initialize repo' " mkdir import && cd import && diff --git a/t/t9105-git-svn-commit-diff.sh b/t/t9105-git-svn-commit-diff.sh index 746c8277d0..6323c7e3ac 100755 --- a/t/t9105-git-svn-commit-diff.sh +++ b/t/t9105-git-svn-commit-diff.sh @@ -4,13 +4,6 @@ test_description='git-svn commit-diff' . ./lib-git-svn.sh -if test -n "$GIT_SVN_NO_LIB" && test "$GIT_SVN_NO_LIB" -ne 0 -then - echo 'Skipping: commit-diff needs SVN libraries' - test_done - exit 0 -fi - test_expect_success 'initialize repo' " mkdir import && cd import && diff --git a/t/t9106-git-svn-commit-diff-clobber.sh b/t/t9106-git-svn-commit-diff-clobber.sh index 58698b3f29..59b6425ce4 100755 --- a/t/t9106-git-svn-commit-diff-clobber.sh +++ b/t/t9106-git-svn-commit-diff-clobber.sh @@ -4,13 +4,6 @@ test_description='git-svn commit-diff clobber' . ./lib-git-svn.sh -if test -n "$GIT_SVN_NO_LIB" && test "$GIT_SVN_NO_LIB" -ne 0 -then - echo 'Skipping: commit-diff clobber needs SVN libraries' - test_done - exit 0 -fi - test_expect_success 'initialize repo' " mkdir import && cd import && diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh index 6e566d4409..ca0513b162 100755 --- a/t/t9200-git-cvsexportcommit.sh +++ b/t/t9200-git-cvsexportcommit.sh @@ -89,18 +89,17 @@ test_expect_success \ ! git cvsexportcommit -c $id )' -# Should fail, but only on the git-cvsexportcommit stage -test_expect_success \ - 'Fail to remove binary file more than one generation old' \ - 'git reset --hard HEAD^ && - cat F/newfile6.png >>D/newfile4.png && - git commit -a -m "generation 2 (again)" && - rm -f D/newfile4.png && - git commit -a -m "generation 3" && - id=$(git rev-list --max-count=1 HEAD) && - (cd "$CVSWORK" && - ! git cvsexportcommit -c $id - )' +#test_expect_success \ +# 'Fail to remove binary file more than one generation old' \ +# 'git reset --hard HEAD^ && +# cat F/newfile6.png >>D/newfile4.png && +# git commit -a -m "generation 2 (again)" && +# rm -f D/newfile4.png && +# git commit -a -m "generation 3" && +# id=$(git rev-list --max-count=1 HEAD) && +# (cd "$CVSWORK" && +# ! git cvsexportcommit -c $id +# )' # We reuse the state from two tests back here @@ -108,7 +107,7 @@ test_expect_success \ # fail with gnu patch, so cvsexportcommit must handle that. test_expect_success \ 'Remove only binary files' \ - 'git reset --hard HEAD^^^ && + 'git reset --hard HEAD^^ && rm -f D/newfile4.png && git commit -a -m "test: remove only a binary file" && id=$(git rev-list --max-count=1 HEAD) && @@ -142,4 +141,73 @@ test_expect_success \ diff F/newfile6.png ../F/newfile6.png )' +test_expect_success \ + 'New file with spaces in file name' \ + 'mkdir "G g" && + echo ok then >"G g/with spaces.txt" && + git add "G g/with spaces.txt" && \ + cp ../test9200a.png "G g/with spaces.png" && \ + git add "G g/with spaces.png" && + git commit -a -m "With spaces" && + id=$(git rev-list --max-count=1 HEAD) && + (cd "$CVSWORK" && + git-cvsexportcommit -c $id && + test "$(echo $(sort "G g/CVS/Entries"|cut -d/ -f2,3,5))" = "with spaces.png/1.1/-kb with spaces.txt/1.1/" + )' + +test_expect_success \ + 'Update file with spaces in file name' \ + 'echo Ok then >>"G g/with spaces.txt" && + cat ../test9200a.png >>"G g/with spaces.png" && \ + git add "G g/with spaces.png" && + git commit -a -m "Update with spaces" && + id=$(git rev-list --max-count=1 HEAD) && + (cd "$CVSWORK" && + git-cvsexportcommit -c $id + test "$(echo $(sort "G g/CVS/Entries"|cut -d/ -f2,3,5))" = "with spaces.png/1.2/-kb with spaces.txt/1.2/" + )' + +# This test contains ISO-8859-1 characters +test_expect_success \ + 'File with non-ascii file name' \ + 'mkdir -p Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö && + echo Foo >Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.txt && + git add Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.txt && + cp ../test9200a.png Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.png && + git add Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.png && + git commit -a -m "Går det så går det" && \ + id=$(git rev-list --max-count=1 HEAD) && + (cd "$CVSWORK" && + git-cvsexportcommit -v -c $id && + test "$(echo $(sort Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/CVS/Entries|cut -d/ -f2,3,5))" = "gårdetsågårdet.png/1.1/-kb gårdetsågårdet.txt/1.1/" + )' + +test_expect_success \ + 'Mismatching patch should fail' \ + 'date >>"E/newfile5.txt" && + git add "E/newfile5.txt" && + git commit -a -m "Update one" && + date >>"E/newfile5.txt" && + git add "E/newfile5.txt" && + git commit -a -m "Update two" && + id=$(git rev-list --max-count=1 HEAD) && + (cd "$CVSWORK" && + ! git-cvsexportcommit -c $id + )' + +test_expect_success \ + 'Retain execute bit' \ + 'mkdir G && + echo executeon >G/on && + chmod +x G/on && + echo executeoff >G/off && + git add G/on && + git add G/off && + git commit -a -m "Execute test" && + (cd "$CVSWORK" && + git-cvsexportcommit -c HEAD + test -x G/on && + ! test -x G/off + )' + test_done diff --git a/t/test-lib.sh b/t/test-lib.sh index 3895f16709..f0f9cd6be0 100755 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -76,7 +76,8 @@ do -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) verbose=t; shift ;; --no-python) - no_python=t; shift ;; + # noop now... + shift ;; *) break ;; esac @@ -207,21 +208,10 @@ test_done () { # t/ subdirectory and are run in trash subdirectory. PATH=$(pwd)/..:$PATH GIT_EXEC_PATH=$(pwd)/.. +GIT_TEMPLATE_DIR=$(pwd)/../templates/blt HOME=$(pwd)/trash -export PATH GIT_EXEC_PATH HOME - -# Similarly use ../compat/subprocess.py if our python does not -# have subprocess.py on its own. -PYTHON=`sed -e '1{ - s/^#!// - q -}' ../git-merge-recursive-old` || { - error "You haven't built things yet, have you?" -} -"$PYTHON" -c 'import subprocess' 2>/dev/null || { - PYTHONPATH=$(pwd)/../compat - export PYTHONPATH -} +export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR HOME + GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git export GITPERLLIB test -d ../templates/blt || { diff --git a/templates/hooks--commit-msg b/templates/hooks--commit-msg index 0b906caa98..9b04f2d69c 100644 --- a/templates/hooks--commit-msg +++ b/templates/hooks--commit-msg @@ -8,6 +8,10 @@ # # To enable this hook, make this file executable. +# Uncomment the below to add a Signed-off-by line to the message. +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + # This example catches duplicate Signed-off-by lines. test "" = "$(grep '^Signed-off-by: ' "$1" | diff --git a/templates/remotes-- b/templates/remotes-- deleted file mode 100644 index fae88709a6..0000000000 --- a/templates/remotes-- +++ /dev/null @@ -1 +0,0 @@ -: this is just to ensure the directory exists. diff --git a/test-date.c b/test-date.c index 93e802759f..62e8f2387a 100644 --- a/test-date.c +++ b/test-date.c @@ -1,6 +1,3 @@ -#include <stdio.h> -#include <time.h> - #include "cache.h" int main(int argc, char **argv) diff --git a/test-delta.c b/test-delta.c index 1be8ee0c72..795aa08377 100644 --- a/test-delta.c +++ b/test-delta.c @@ -8,13 +8,7 @@ * published by the Free Software Foundation. */ -#include <stdio.h> -#include <unistd.h> -#include <string.h> -#include <fcntl.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <sys/mman.h> +#include "git-compat-util.h" #include "delta.h" static const char usage[] = @@ -4,7 +4,6 @@ #include "commit.h" #include "tag.h" #include "tree-walk.h" -#include <stdlib.h> const char *tree_type = "tree"; diff --git a/unpack-trees.c b/unpack-trees.c index 7cfd628d8e..2e2232cbb0 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -1,6 +1,5 @@ -#include <signal.h> -#include <sys/time.h> #include "cache.h" +#include "dir.h" #include "tree.h" #include "tree-walk.h" #include "cache-tree.h" @@ -77,6 +76,12 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, { int baselen = strlen(base); int src_size = len + 1; + int i_stk = i_stk; + int retval = 0; + + if (o->dir) + i_stk = push_exclude_per_directory(o->dir, base, strlen(base)); + do { int i; const char *first; @@ -143,7 +148,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, } /* No name means we're done */ if (!first) - return 0; + goto leave_directory; pathlen = strlen(first); ce_size = cache_entry_size(baselen + pathlen); @@ -240,13 +245,20 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, newbase[baselen + pathlen] = '/'; newbase[baselen + pathlen + 1] = '\0'; if (unpack_trees_rec(subposns, len, newbase, o, - indpos, df_conflict_list)) - return -1; + indpos, df_conflict_list)) { + retval = -1; + goto leave_directory; + } free(newbase); } free(subposns); free(src); } while (1); + + leave_directory: + if (o->dir) + pop_exclude_per_directory(o->dir, i_stk); + return retval; } /* Unlink the last component and attempt to remove leading @@ -370,7 +382,7 @@ int unpack_trees(struct object_list *trees, struct unpack_trees_options *o) int i; struct object_list *posn = trees; struct tree_entry_list df_conflict_list; - struct cache_entry df_conflict_entry; + static struct cache_entry *dfc; memset(&df_conflict_list, 0, sizeof(df_conflict_list)); df_conflict_list.next = &df_conflict_list; @@ -381,8 +393,10 @@ int unpack_trees(struct object_list *trees, struct unpack_trees_options *o) state.refresh_cache = 1; o->merge_size = len; - memset(&df_conflict_entry, 0, sizeof(df_conflict_entry)); - o->df_conflict_entry = &df_conflict_entry; + + if (!dfc) + dfc = xcalloc(1, sizeof(struct cache_entry) + 1); + o->df_conflict_entry = dfc; if (len) { posns = xmalloc(len * sizeof(struct tree_entry_list *)); @@ -456,7 +470,7 @@ static void invalidate_ce_path(struct cache_entry *ce) /* * We do not want to remove or overwrite a working tree file that - * is not tracked. + * is not tracked, unless it is ignored. */ static void verify_absent(const char *path, const char *action, struct unpack_trees_options *o) @@ -465,7 +479,7 @@ static void verify_absent(const char *path, const char *action, if (o->index_only || o->reset || !o->update) return; - if (!lstat(path, &st)) + if (!lstat(path, &st) && !(o->dir && excluded(o->dir, path))) die("Untracked working tree file '%s' " "would be %s by merge.", path, action); } diff --git a/unpack-trees.h b/unpack-trees.h index c4601621cd..191f7442f1 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -16,6 +16,7 @@ struct unpack_trees_options { int verbose_update; int aggressive; const char *prefix; + struct dir_struct *dir; merge_fn_t fn; int head_idx; diff --git a/upload-pack.c b/upload-pack.c index d4a7b625f4..c568ef066c 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -1,6 +1,3 @@ -#include <signal.h> -#include <sys/wait.h> -#include <sys/poll.h> #include "cache.h" #include "refs.h" #include "pkt-line.h" @@ -29,12 +29,17 @@ static void error_builtin(const char *err, va_list params) report("error: ", err, params); } +static void warn_builtin(const char *warn, va_list params) +{ + report("warning: ", warn, params); +} /* If we are in a dlopen()ed .so write to a global variable would segfault * (ugh), so keep things static. */ static void (*usage_routine)(const char *err) NORETURN = usage_builtin; static void (*die_routine)(const char *err, va_list params) NORETURN = die_builtin; static void (*error_routine)(const char *err, va_list params) = error_builtin; +static void (*warn_routine)(const char *err, va_list params) = warn_builtin; void set_usage_routine(void (*routine)(const char *err) NORETURN) { @@ -51,6 +56,11 @@ void set_error_routine(void (*routine)(const char *err, va_list params)) error_routine = routine; } +void set_warn_routine(void (*routine)(const char *warn, va_list params)) +{ + warn_routine = routine; +} + void usage(const char *err) { @@ -75,3 +85,12 @@ int error(const char *err, ...) va_end(params); return -1; } + +void warn(const char *warn, ...) +{ + va_list params; + + va_start(params, warn); + warn_routine(warn, params); + va_end(params); +} diff --git a/utf8.c b/utf8.c new file mode 100644 index 0000000000..8fa62571aa --- /dev/null +++ b/utf8.c @@ -0,0 +1,278 @@ +#include "git-compat-util.h" +#include "utf8.h" + +/* This code is originally from http://www.cl.cam.ac.uk/~mgk25/ucs/ */ + +struct interval { + int first; + int last; +}; + +/* auxiliary function for binary search in interval table */ +static int bisearch(wchar_t ucs, const struct interval *table, int max) { + int min = 0; + int mid; + + if (ucs < table[0].first || ucs > table[max].last) + return 0; + while (max >= min) { + mid = (min + max) / 2; + if (ucs > table[mid].last) + min = mid + 1; + else if (ucs < table[mid].first) + max = mid - 1; + else + return 1; + } + + return 0; +} + +/* The following two functions define the column width of an ISO 10646 + * character as follows: + * + * - The null character (U+0000) has a column width of 0. + * + * - Other C0/C1 control characters and DEL will lead to a return + * value of -1. + * + * - Non-spacing and enclosing combining characters (general + * category code Mn or Me in the Unicode database) have a + * column width of 0. + * + * - SOFT HYPHEN (U+00AD) has a column width of 1. + * + * - Other format characters (general category code Cf in the Unicode + * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. + * + * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) + * have a column width of 0. + * + * - Spacing characters in the East Asian Wide (W) or East Asian + * Full-width (F) category as defined in Unicode Technical + * Report #11 have a column width of 2. + * + * - All remaining characters (including all printable + * ISO 8859-1 and WGL4 characters, Unicode control characters, + * etc.) have a column width of 1. + * + * This implementation assumes that wchar_t characters are encoded + * in ISO 10646. + */ + +static int wcwidth(wchar_t ch) +{ + /* + * Sorted list of non-overlapping intervals of non-spacing characters, + * generated by + * "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c". + */ + static const struct interval combining[] = { + { 0x0300, 0x0357 }, { 0x035D, 0x036F }, { 0x0483, 0x0486 }, + { 0x0488, 0x0489 }, { 0x0591, 0x05A1 }, { 0x05A3, 0x05B9 }, + { 0x05BB, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, + { 0x05C4, 0x05C4 }, { 0x0600, 0x0603 }, { 0x0610, 0x0615 }, + { 0x064B, 0x0658 }, { 0x0670, 0x0670 }, { 0x06D6, 0x06E4 }, + { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, { 0x070F, 0x070F }, + { 0x0711, 0x0711 }, { 0x0730, 0x074A }, { 0x07A6, 0x07B0 }, + { 0x0901, 0x0902 }, { 0x093C, 0x093C }, { 0x0941, 0x0948 }, + { 0x094D, 0x094D }, { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, + { 0x0981, 0x0981 }, { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, + { 0x09CD, 0x09CD }, { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, + { 0x0A3C, 0x0A3C }, { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, + { 0x0A4B, 0x0A4D }, { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, + { 0x0ABC, 0x0ABC }, { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, + { 0x0ACD, 0x0ACD }, { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, + { 0x0B3C, 0x0B3C }, { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, + { 0x0B4D, 0x0B4D }, { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, + { 0x0BC0, 0x0BC0 }, { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, + { 0x0C46, 0x0C48 }, { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, + { 0x0CBC, 0x0CBC }, { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, + { 0x0CCC, 0x0CCD }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, + { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, + { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, + { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, + { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, + { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, + { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, + { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, + { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, + { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x1712, 0x1714 }, + { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, { 0x1772, 0x1773 }, + { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, { 0x17C6, 0x17C6 }, + { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, { 0x180B, 0x180D }, + { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, { 0x1927, 0x1928 }, + { 0x1932, 0x1932 }, { 0x1939, 0x193B }, { 0x200B, 0x200F }, + { 0x202A, 0x202E }, { 0x2060, 0x2063 }, { 0x206A, 0x206F }, + { 0x20D0, 0x20EA }, { 0x302A, 0x302F }, { 0x3099, 0x309A }, + { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, { 0xFE20, 0xFE23 }, + { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, { 0x1D167, 0x1D169 }, + { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, + { 0x1D1AA, 0x1D1AD }, { 0xE0001, 0xE0001 }, + { 0xE0020, 0xE007F }, { 0xE0100, 0xE01EF } + }; + + /* test for 8-bit control characters */ + if (ch == 0) + return 0; + if (ch < 32 || (ch >= 0x7f && ch < 0xa0)) + return -1; + + /* binary search in table of non-spacing characters */ + if (bisearch(ch, combining, sizeof(combining) + / sizeof(struct interval) - 1)) + return 0; + + /* + * If we arrive here, ch is neither a combining nor a C0/C1 + * control character. + */ + + return 1 + + (ch >= 0x1100 && + /* Hangul Jamo init. consonants */ + (ch <= 0x115f || + ch == 0x2329 || ch == 0x232a || + /* CJK ... Yi */ + (ch >= 0x2e80 && ch <= 0xa4cf && + ch != 0x303f) || + /* Hangul Syllables */ + (ch >= 0xac00 && ch <= 0xd7a3) || + /* CJK Compatibility Ideographs */ + (ch >= 0xf900 && ch <= 0xfaff) || + /* CJK Compatibility Forms */ + (ch >= 0xfe30 && ch <= 0xfe6f) || + /* Fullwidth Forms */ + (ch >= 0xff00 && ch <= 0xff60) || + (ch >= 0xffe0 && ch <= 0xffe6) || + (ch >= 0x20000 && ch <= 0x2fffd) || + (ch >= 0x30000 && ch <= 0x3fffd))); +} + +/* + * This function returns the number of columns occupied by the character + * pointed to by the variable start. The pointer is updated to point at + * the next character. If it was not valid UTF-8, the pointer is set to NULL. + */ +int utf8_width(const char **start) +{ + unsigned char *s = (unsigned char *)*start; + wchar_t ch; + + if (*s < 0x80) { + /* 0xxxxxxx */ + ch = *s; + *start += 1; + } else if ((s[0] & 0xe0) == 0xc0) { + /* 110XXXXx 10xxxxxx */ + if ((s[1] & 0xc0) != 0x80 || + /* overlong? */ + (s[0] & 0xfe) == 0xc0) + goto invalid; + ch = ((s[0] & 0x1f) << 6) | (s[1] & 0x3f); + *start += 2; + } else if ((s[0] & 0xf0) == 0xe0) { + /* 1110XXXX 10Xxxxxx 10xxxxxx */ + if ((s[1] & 0xc0) != 0x80 || + (s[2] & 0xc0) != 0x80 || + /* overlong? */ + (s[0] == 0xe0 && (s[1] & 0xe0) == 0x80) || + /* surrogate? */ + (s[0] == 0xed && (s[1] & 0xe0) == 0xa0) || + /* U+FFFE or U+FFFF? */ + (s[0] == 0xef && s[1] == 0xbf && + (s[2] & 0xfe) == 0xbe)) + goto invalid; + ch = ((s[0] & 0x0f) << 12) | + ((s[1] & 0x3f) << 6) | (s[2] & 0x3f); + *start += 3; + } else if ((s[0] & 0xf8) == 0xf0) { + /* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */ + if ((s[1] & 0xc0) != 0x80 || + (s[2] & 0xc0) != 0x80 || + (s[3] & 0xc0) != 0x80 || + /* overlong? */ + (s[0] == 0xf0 && (s[1] & 0xf0) == 0x80) || + /* > U+10FFFF? */ + (s[0] == 0xf4 && s[1] > 0x8f) || s[0] > 0xf4) + goto invalid; + ch = ((s[0] & 0x07) << 18) | ((s[1] & 0x3f) << 12) | + ((s[2] & 0x3f) << 6) | (s[3] & 0x3f); + *start += 4; + } else { +invalid: + *start = NULL; + return 0; + } + + return wcwidth(ch); +} + +int is_utf8(const char *text) +{ + while (*text) { + if (*text == '\n' || *text == '\t' || *text == '\r') { + text++; + continue; + } + utf8_width(&text); + if (!text) + return 0; + } + return 1; +} + +static void print_spaces(int count) +{ + static const char s[] = " "; + while (count >= sizeof(s)) { + fwrite(s, sizeof(s) - 1, 1, stdout); + count -= sizeof(s) - 1; + } + fwrite(s, count, 1, stdout); +} + +/* + * Wrap the text, if necessary. The variable indent is the indent for the + * first line, indent2 is the indent for all other lines. + */ +void print_wrapped_text(const char *text, int indent, int indent2, int width) +{ + int w = indent, assume_utf8 = is_utf8(text); + const char *bol = text, *space = NULL; + + for (;;) { + char c = *text; + if (!c || isspace(c)) { + if (w < width || !space) { + const char *start = bol; + if (space) + start = space; + else + print_spaces(indent); + fwrite(start, text - start, 1, stdout); + if (!c) { + putchar('\n'); + return; + } else if (c == '\t') + w |= 0x07; + space = text; + w++; + text++; + } + else { + putchar('\n'); + text = bol = space + 1; + space = NULL; + w = indent = indent2; + } + continue; + } + if (assume_utf8) + w += utf8_width(&text); + else { + w++; + text++; + } + } +} diff --git a/utf8.h b/utf8.h new file mode 100644 index 0000000000..a0d7f591ad --- /dev/null +++ b/utf8.h @@ -0,0 +1,8 @@ +#ifndef GIT_UTF8_H +#define GIT_UTF8_H + +int utf8_width(const char **start); +int is_utf8(const char *text); +void print_wrapped_text(const char *text, int indent, int indent2, int len); + +#endif @@ -4,9 +4,6 @@ * Copyright (C) Eric Biederman, 2005 */ #include "cache.h" -#include <stdio.h> -#include <errno.h> -#include <string.h> static const char var_usage[] = "git-var [-l | <variable>]"; diff --git a/wt-status.c b/wt-status.c index de1be5bc66..db427384ff 100644 --- a/wt-status.c +++ b/wt-status.c @@ -1,6 +1,6 @@ +#include "cache.h" #include "wt-status.h" #include "color.h" -#include "cache.h" #include "object.h" #include "dir.h" #include "commit.h" @@ -15,12 +15,14 @@ static char wt_status_colors[][COLOR_MAXLEN] = { "\033[31m", /* WT_STATUS_CHANGED: red */ "\033[31m", /* WT_STATUS_UNTRACKED: red */ }; +static const char* use_add_msg = "use \"git add file1 file2\" to include for commit"; static int parse_status_slot(const char *var, int offset) { if (!strcasecmp(var+offset, "header")) return WT_STATUS_HEADER; - if (!strcasecmp(var+offset, "updated")) + if (!strcasecmp(var+offset, "updated") + || !strcasecmp(var+offset, "added")) return WT_STATUS_UPDATED; if (!strcasecmp(var+offset, "changed")) return WT_STATUS_CHANGED; @@ -145,7 +147,7 @@ static void wt_status_print_updated_cb(struct diff_queue_struct *q, if (q->queue[i]->status == 'U') continue; if (!shown_header) { - wt_status_print_header("Updated but not checked in", + wt_status_print_header("Added but not yet committed", "will commit"); s->commitable = 1; shown_header = 1; @@ -162,8 +164,7 @@ static void wt_status_print_changed_cb(struct diff_queue_struct *q, { int i; if (q->nr) - wt_status_print_header("Changed but not updated", - "use git-update-index to mark for commit"); + wt_status_print_header("Changed but not added", use_add_msg); for (i = 0; i < q->nr; i++) wt_status_print_filepair(WT_STATUS_CHANGED, q->queue[i]); if (q->nr) @@ -178,7 +179,7 @@ void wt_status_print_initial(struct wt_status *s) read_cache(); if (active_nr) { s->commitable = 1; - wt_status_print_header("Updated but not checked in", + wt_status_print_header("Added but not yet committed", "will commit"); } for (i = 0; i < active_nr; i++) { @@ -249,8 +250,7 @@ static void wt_status_print_untracked(const struct wt_status *s) continue; } if (!shown_header) { - wt_status_print_header("Untracked files", - "use \"git add\" to add to commit"); + wt_status_print_header("Untracked files", use_add_msg); shown_header = 1; } color_printf(color(WT_STATUS_HEADER), "#\t"); @@ -271,7 +271,7 @@ static void wt_status_print_verbose(struct wt_status *s) void wt_status_print(struct wt_status *s) { - if (s->branch && strcmp(s->branch, "refs/heads/master")) + if (s->branch) color_printf_ln(color(WT_STATUS_HEADER), "# On branch %s", s->branch); @@ -292,16 +292,18 @@ void wt_status_print(struct wt_status *s) if (s->verbose && !s->is_initial) wt_status_print_verbose(s); if (!s->commitable) - printf("%s\n", s->amend ? "# No changes" : "nothing to commit"); + printf("%s (%s)\n", + s->amend ? "# No changes" : "nothing to commit", + use_add_msg); } int git_status_config(const char *k, const char *v) { - if (!strcmp(k, "status.color")) { + if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) { wt_status_use_color = git_config_colorbool(k, v); return 0; } - if (!strncmp(k, "status.color.", 13)) { + if (!strncmp(k, "status.color.", 13) || !strncmp(k, "color.status", 13)) { int slot = parse_status_slot(k, 13); color_parse(v, k, wt_status_colors[slot]); } diff --git a/xdiff-interface.c b/xdiff-interface.c index 08602f5221..6c1f99b149 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -102,3 +102,22 @@ int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf) } return 0; } + +int read_mmfile(mmfile_t *ptr, const char *filename) +{ + struct stat st; + FILE *f; + + if (stat(filename, &st)) + return error("Could not stat %s", filename); + if ((f = fopen(filename, "rb")) == NULL) + return error("Could not open %s", filename); + ptr->ptr = xmalloc(st.st_size); + if (fread(ptr->ptr, st.st_size, 1, f) != 1) + return error("Could not read %s", filename); + fclose(f); + ptr->size = st.st_size; + return 0; +} + + diff --git a/xdiff-interface.h b/xdiff-interface.h index 1346908bea..1918808081 100644 --- a/xdiff-interface.h +++ b/xdiff-interface.h @@ -17,5 +17,6 @@ int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf); int parse_hunk_header(char *line, int len, int *ob, int *on, int *nb, int *nn); +int read_mmfile(mmfile_t *ptr, const char *filename); #endif diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h index c9f817818a..fa409d5234 100644 --- a/xdiff/xdiff.h +++ b/xdiff/xdiff.h @@ -49,6 +49,9 @@ extern "C" { #define XDL_BDOP_CPY 2 #define XDL_BDOP_INSB 3 +#define XDL_MERGE_MINIMAL 0 +#define XDL_MERGE_EAGER 1 +#define XDL_MERGE_ZEALOUS 2 typedef struct s_mmfile { char *ptr; @@ -90,6 +93,10 @@ long xdl_mmfile_size(mmfile_t *mmf); int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t const *xecfg, xdemitcb_t *ecb); +int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, + mmfile_t *mf2, const char *name2, + xpparam_t const *xpp, int level, mmbuffer_t *result); + #ifdef __cplusplus } #endif /* #ifdef __cplusplus */ diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c index d76e76a0e6..9aeebc473b 100644 --- a/xdiff/xdiffi.c +++ b/xdiff/xdiffi.c @@ -45,7 +45,6 @@ static long xdl_split(unsigned long const *ha1, long off1, long lim1, long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl, xdalgoenv_t *xenv); static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, long chg2); -static int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags); @@ -397,7 +396,7 @@ static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, } -static int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { +int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { long ix, ixo, ixs, ixref, grpsiz, nrec = xdf->nrec; char *rchg = xdf->rchg, *rchgo = xdfo->rchg; xrecord_t **recs = xdf->recs; diff --git a/xdiff/xdiffi.h b/xdiff/xdiffi.h index d3b72716b5..472aeaecfa 100644 --- a/xdiff/xdiffi.h +++ b/xdiff/xdiffi.h @@ -50,6 +50,7 @@ int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1, long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv); int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdfenv_t *xe); +int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags); int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr); void xdl_free_script(xdchange_t *xscr); int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c new file mode 100644 index 0000000000..352207e516 --- /dev/null +++ b/xdiff/xmerge.c @@ -0,0 +1,419 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003-2006 Davide Libenzi, Johannes E. Schindelin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#include "xinclude.h" + +typedef struct s_xdmerge { + struct s_xdmerge *next; + /* + * 0 = conflict, + * 1 = no conflict, take first, + * 2 = no conflict, take second. + */ + int mode; + long i1, i2; + long chg1, chg2; +} xdmerge_t; + +static int xdl_append_merge(xdmerge_t **merge, int mode, + long i1, long chg1, long i2, long chg2) +{ + xdmerge_t *m = *merge; + if (m && (i1 <= m->i1 + m->chg1 || i2 <= m->i2 + m->chg2)) { + if (mode != m->mode) + m->mode = 0; + m->chg1 = i1 + chg1 - m->i1; + m->chg2 = i2 + chg2 - m->i2; + } else { + m = xdl_malloc(sizeof(xdmerge_t)); + if (!m) + return -1; + m->next = NULL; + m->mode = mode; + m->i1 = i1; + m->chg1 = chg1; + m->i2 = i2; + m->chg2 = chg2; + if (*merge) + (*merge)->next = m; + *merge = m; + } + return 0; +} + +static int xdl_cleanup_merge(xdmerge_t *c) +{ + int count = 0; + xdmerge_t *next_c; + + /* were there conflicts? */ + for (; c; c = next_c) { + if (c->mode == 0) + count++; + next_c = c->next; + free(c); + } + return count; +} + +static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2, + int line_count, long flags) +{ + int i; + xrecord_t **rec1 = xe1->xdf2.recs + i1; + xrecord_t **rec2 = xe2->xdf2.recs + i2; + + for (i = 0; i < line_count; i++) { + int result = xdl_recmatch(rec1[i]->ptr, rec1[i]->size, + rec2[i]->ptr, rec2[i]->size, flags); + if (!result) + return -1; + } + return 0; +} + +static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest) +{ + xrecord_t **recs = xe->xdf2.recs + i; + int size = 0; + + if (count < 1) + return 0; + + for (i = 0; i < count; size += recs[i++]->size) + if (dest) + memcpy(dest + size, recs[i]->ptr, recs[i]->size); + if (add_nl) { + i = recs[count - 1]->size; + if (i == 0 || recs[count - 1]->ptr[i - 1] != '\n') { + if (dest) + dest[size] = '\n'; + size++; + } + } + return size; +} + +static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, + xdfenv_t *xe2, const char *name2, xdmerge_t *m, char *dest) +{ + const int marker_size = 7; + int marker1_size = (name1 ? strlen(name1) + 1 : 0); + int marker2_size = (name2 ? strlen(name2) + 1 : 0); + int conflict_marker_size = 3 * (marker_size + 1) + + marker1_size + marker2_size; + int size, i1, j; + + for (size = i1 = 0; m; m = m->next) { + if (m->mode == 0) { + size += xdl_recs_copy(xe1, i1, m->i1 - i1, 0, + dest ? dest + size : NULL); + if (dest) { + for (j = 0; j < marker_size; j++) + dest[size++] = '<'; + if (marker1_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name1, + marker1_size - 1); + size += marker1_size; + } + dest[size++] = '\n'; + } else + size += conflict_marker_size; + size += xdl_recs_copy(xe1, m->i1, m->chg1, 1, + dest ? dest + size : NULL); + if (dest) { + for (j = 0; j < marker_size; j++) + dest[size++] = '='; + dest[size++] = '\n'; + } + size += xdl_recs_copy(xe2, m->i2, m->chg2, 1, + dest ? dest + size : NULL); + if (dest) { + for (j = 0; j < marker_size; j++) + dest[size++] = '>'; + if (marker2_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name2, + marker2_size - 1); + size += marker2_size; + } + dest[size++] = '\n'; + } + } else if (m->mode == 1) + size += xdl_recs_copy(xe1, i1, m->i1 + m->chg1 - i1, 0, + dest ? dest + size : NULL); + else if (m->mode == 2) + size += xdl_recs_copy(xe2, m->i2 - m->i1 + i1, + m->i1 + m->chg2 - i1, 0, + dest ? dest + size : NULL); + i1 = m->i1 + m->chg1; + } + size += xdl_recs_copy(xe1, i1, xe1->xdf2.nrec - i1, 0, + dest ? dest + size : NULL); + return size; +} + +/* + * Sometimes, changes are not quite identical, but differ in only a few + * lines. Try hard to show only these few lines as conflicting. + */ +static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m, + xpparam_t const *xpp) +{ + for (; m; m = m->next) { + mmfile_t t1, t2; + xdfenv_t xe; + xdchange_t *xscr, *x; + int i1 = m->i1, i2 = m->i2; + + /* let's handle just the conflicts */ + if (m->mode) + continue; + + /* + * This probably does not work outside git, since + * we have a very simple mmfile structure. + */ + t1.ptr = (char *)xe1->xdf2.recs[m->i1]->ptr; + t1.size = xe1->xdf2.recs[m->i1 + m->chg1 - 1]->ptr + + xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - t1.ptr; + t2.ptr = (char *)xe2->xdf2.recs[m->i2]->ptr; + t2.size = xe2->xdf2.recs[m->i2 + m->chg2 - 1]->ptr + + xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - t2.ptr; + if (xdl_do_diff(&t1, &t2, xpp, &xe) < 0) + return -1; + if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe, &xscr) < 0) { + xdl_free_env(&xe); + return -1; + } + if (!xscr) { + /* If this happens, it's a bug. */ + xdl_free_env(&xe); + return -2; + } + x = xscr; + m->i1 = xscr->i1 + i1; + m->chg1 = xscr->chg1; + m->i2 = xscr->i2 + i2; + m->chg2 = xscr->chg2; + while (xscr->next) { + xdmerge_t *m2 = xdl_malloc(sizeof(xdmerge_t)); + if (!m2) { + xdl_free_env(&xe); + xdl_free_script(x); + return -1; + } + xscr = xscr->next; + m2->next = m->next; + m->next = m2; + m = m2; + m->mode = 0; + m->i1 = xscr->i1 + i1; + m->chg1 = xscr->chg1; + m->i2 = xscr->i2 + i2; + m->chg2 = xscr->chg2; + } + xdl_free_env(&xe); + xdl_free_script(x); + } + return 0; +} + +/* + * level == 0: mark all overlapping changes as conflict + * level == 1: mark overlapping changes as conflict only if not identical + * level == 2: analyze non-identical changes for minimal conflict set + * + * returns < 0 on error, == 0 for no conflicts, else number of conflicts + */ +static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, + xdfenv_t *xe2, xdchange_t *xscr2, const char *name2, + int level, xpparam_t const *xpp, mmbuffer_t *result) { + xdmerge_t *changes, *c; + int i1, i2, chg1, chg2; + + c = changes = NULL; + + while (xscr1 && xscr2) { + if (!changes) + changes = c; + if (xscr1->i1 + xscr1->chg1 < xscr2->i1) { + i1 = xscr1->i2; + i2 = xscr2->i2 - xscr2->i1 + xscr1->i1; + chg1 = xscr1->chg2; + chg2 = xscr1->chg1; + if (xdl_append_merge(&c, 1, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr1 = xscr1->next; + continue; + } + if (xscr2->i1 + xscr2->chg1 < xscr1->i1) { + i1 = xscr1->i2 - xscr1->i1 + xscr2->i1; + i2 = xscr2->i2; + chg1 = xscr2->chg1; + chg2 = xscr2->chg2; + if (xdl_append_merge(&c, 2, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr2 = xscr2->next; + continue; + } + if (level < 1 || xscr1->i1 != xscr2->i1 || + xscr1->chg1 != xscr2->chg1 || + xscr1->chg2 != xscr2->chg2 || + xdl_merge_cmp_lines(xe1, xscr1->i2, + xe2, xscr2->i2, + xscr1->chg2, xpp->flags)) { + /* conflict */ + int off = xscr1->i1 - xscr2->i1; + int ffo = off + xscr1->chg1 - xscr2->chg1; + + i1 = xscr1->i2; + i2 = xscr2->i2; + if (off > 0) + i1 -= off; + else + i2 += off; + chg1 = xscr1->i2 + xscr1->chg2 - i1; + chg2 = xscr2->i2 + xscr2->chg2 - i2; + if (ffo > 0) + chg2 += ffo; + else + chg1 -= ffo; + if (xdl_append_merge(&c, 0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + } + + i1 = xscr1->i1 + xscr1->chg1; + i2 = xscr2->i1 + xscr2->chg1; + + if (i1 >= i2) + xscr2 = xscr2->next; + if (i2 >= i1) + xscr1 = xscr1->next; + } + while (xscr1) { + if (!changes) + changes = c; + i1 = xscr1->i2; + i2 = xscr1->i1 + xe2->xdf2.nrec - xe2->xdf1.nrec; + chg1 = xscr1->chg2; + chg2 = xscr1->chg1; + if (xdl_append_merge(&c, 1, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr1 = xscr1->next; + } + while (xscr2) { + if (!changes) + changes = c; + i1 = xscr2->i1 + xe1->xdf2.nrec - xe1->xdf1.nrec; + i2 = xscr2->i2; + chg1 = xscr2->chg1; + chg2 = xscr2->chg2; + if (xdl_append_merge(&c, 2, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr2 = xscr2->next; + } + if (!changes) + changes = c; + /* refine conflicts */ + if (level > 1 && xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0) { + xdl_cleanup_merge(changes); + return -1; + } + /* output */ + if (result) { + int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2, + changes, NULL); + result->ptr = xdl_malloc(size); + if (!result->ptr) { + xdl_cleanup_merge(changes); + return -1; + } + result->size = size; + xdl_fill_merge_buffer(xe1, name1, xe2, name2, changes, + result->ptr); + } + return xdl_cleanup_merge(changes); +} + +int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, + mmfile_t *mf2, const char *name2, + xpparam_t const *xpp, int level, mmbuffer_t *result) { + xdchange_t *xscr1, *xscr2; + xdfenv_t xe1, xe2; + int status; + + result->ptr = NULL; + result->size = 0; + + if (xdl_do_diff(orig, mf1, xpp, &xe1) < 0 || + xdl_do_diff(orig, mf2, xpp, &xe2) < 0) { + return -1; + } + if (xdl_change_compact(&xe1.xdf1, &xe1.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe1.xdf2, &xe1.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe1, &xscr1) < 0) { + xdl_free_env(&xe1); + return -1; + } + if (xdl_change_compact(&xe2.xdf1, &xe2.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe2.xdf2, &xe2.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe2, &xscr2) < 0) { + xdl_free_env(&xe2); + return -1; + } + status = 0; + if (xscr1 || xscr2) { + if (!xscr1) { + result->ptr = xdl_malloc(mf2->size); + memcpy(result->ptr, mf2->ptr, mf2->size); + result->size = mf2->size; + } else if (!xscr2) { + result->ptr = xdl_malloc(mf1->size); + memcpy(result->ptr, mf1->ptr, mf1->size); + result->size = mf1->size; + } else { + status = xdl_do_merge(&xe1, xscr1, name1, + &xe2, xscr2, name2, + level, xpp, result); + } + xdl_free_script(xscr1); + xdl_free_script(xscr2); + } + xdl_free_env(&xe1); + xdl_free_env(&xe2); + + return status; +} diff --git a/xdiff/xutils.c b/xdiff/xutils.c index 9e4bb47ee9..1b899f32c4 100644 --- a/xdiff/xutils.c +++ b/xdiff/xutils.c @@ -230,7 +230,8 @@ unsigned long xdl_hash_record(char const **data, char const *top, long flags) { while (ptr + 1 < top && isspace(ptr[1]) && ptr[1] != '\n') ptr++; - if (flags & XDF_IGNORE_WHITESPACE_CHANGE) { + if (flags & XDF_IGNORE_WHITESPACE_CHANGE + && ptr[1] != '\n') { ha += (ha << 5); ha ^= (unsigned long) ' '; } |