diff --git a/Documentation/Makefile b/Documentation/Makefile index c00f5f62b7..d68bc4a788 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -56,8 +56,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) # diff --git a/Documentation/config.txt b/Documentation/config.txt index 9090762819..21ec55797b 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -125,10 +125,17 @@ apply.whitespace:: branch..remote:: When in branch , it tells `git fetch` which remote to fetch. + If this option is not given, `git fetch` defaults to remote "origin". branch..merge:: - When in branch , it tells `git fetch` the default remote branch - to be merged. + When in branch , 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..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. pager.color:: A boolean to enable/disable colored output when the pager is in 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,43 +1,122 @@ 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. +Suppose a shared repository is set up in /pub/repo.git on the host +foo.com. Then as an individual committer you can clone the shared +repository over ssh with: - * Offline work is supported: you can make multiple commits locally, - then submit them when you're ready. +------------------------------------------------ +$ git clone foo.com:/pub/repo.git/ my-project +$ cd my-project +------------------------------------------------ - * Branching is fast and easy. +and hack away. The equivalent of `cvs update` is - * 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. +------------------------------------------------ +$ git pull origin +------------------------------------------------ + +which merges in any work that others might have done since the clone +operation. If there are uncommitted changes in your working tree, commit +them first before running git pull. + +[NOTE] +================================ +The 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/ +Pull: refs/heads/master:refs/remotes/origin/master +------------ +================================ + +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 +------------------------------------------------ + +to "push" those commits to the shared repository. If someone else has +updated the repository more recently, `git push`, like `cvs commit`, will +complain, in which case you must pull any changes before attempting the +push again. + +In the `git push` command above we specify the name of the remote branch +to update (`master`). If we leave that out, `git push` tries to update +any branches in the remote repository that have the same name as a branch +in the local repository. So the last `push` can be done with either of: + +------------ +$ git push origin +$ git push foo.com:/pub/project.git/ +------------ + +as long as the shared repository does not have any branches +other than `master`. + +Setting Up a Shared Repository +------------------------------ + +We assume you have already created a git repository for your project, +possibly created from scratch or from a tarball (see the +link:tutorial.html[tutorial]), or imported from an already existing CVS +repository (see the next section). + +Assume your existing repo is at /home/alice/myproject. Create a new "bare" +repository (a repository without a working tree) and fetch your project into +it: + +------------------------------------------------ +$ mkdir /pub/my-repo.git +$ cd /pub/my-repo.git +$ git --bare init-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. The magic command line is then +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 -v -d -C +$ git cvsimport -C ------------------------------------------- This puts a git archive of the named CVS module in the directory -, which will be created if necessary. The -v option makes -the conversion script very chatty. +, 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 @@ -55,14 +134,32 @@ 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 ------------------- +Advanced Shared Repository Management +------------------------------------- + +Git allows you to specify scripts called "hooks" to be run at certain +points. You can use these, for example, to send all commits to the shared +repository to a mailing list. See link:hooks.html[Hooks used by git]. + +You can enforce finer grained permissions using update hooks. See +link:howto/update-hook-example.txt[Controlling access to branches using +update hooks]. + +Providing CVS Access to a git Repository +---------------------------------------- + +It is also possible to provide true CVS access to a git repository, so +that developers can still use CVS; see 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. 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. +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 @@ -75,230 +172,3 @@ 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 -foo.com. Then as an individual committer you can clone the shared -repository: - ------------------------------------------------- -$ git clone foo.com:/pub/repo.git/ my-project -$ cd my-project ------------------------------------------------- - -and hack away. The equivalent of `cvs update` is - ------------------------------------------------- -$ git pull origin ------------------------------------------------- - -which merges in any work that others might have done since the clone -operation. - -[NOTE] -================================ -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 ------------- -================================ - -You can update the shared repository with your changes using: - ------------------------------------------------- -$ 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. - -In the `git push` command above we specify the name of the remote branch -to update (`master`). If we leave that out, `git push` tries to update -any branches in the remote repository that have the same name as a branch -in the local repository. So the last `push` can be done with either of: - ------------- -$ git push origin -$ git push repo.shared.xz:/pub/scm/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. -============ - -Advanced Shared Repository Management -------------------------------------- - -Git allows you to specify scripts called "hooks" to be run at certain -points. You can use these, for example, to send all commits to the shared -repository to a mailing list. See link:hooks.html[Hooks used by git]. - -You can enforce finer grained permissions using update hooks. See -link:howto/update-hook-example.txt[Controlling access to branches using -update hooks]. - -CVS annotate ------------- - -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. diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index e112172ca5..9cdd171af7 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -129,5 +129,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/git-clone.txt b/Documentation/git-clone.txt index 4cb42237b5..985043faca 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -11,27 +11,26 @@ SYNOPSIS [verse] 'git-clone' [--template=] [-l [-s]] [-q] [-n] [--bare] [-o ] [-u ] [--reference ] - [--use-separate-remote | --use-immingled-remote] + [--use-separate-remote | --no-separate-remote] [] 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 ------- @@ -105,7 +104,7 @@ OPTIONS of `$GIT_DIR/refs/heads/`. Only the local master branch is saved in the latter. This is the default. ---use-immingled-remote:: +--no-separate-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 diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index a45067e164..c589a98630 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -57,11 +57,13 @@ See '<>' 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. + An optional command-line argument may be specified as an + alternative to HEAD. This is advantageous over 'commit' (below) because it produces cleaner, more linear history. diff --git a/Makefile b/Makefile index 79cb91feec..2d17fa7027 100644 --- a/Makefile +++ b/Makefile @@ -91,6 +91,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 @@ -323,18 +327,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 @@ -412,6 +404,21 @@ endif -include config.mak.autogen -include config.mak +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 + ifndef NO_CURL ifdef CURLDIR # This is still problematic -- gcc does not always want -R. @@ -540,6 +547,9 @@ endif ifdef NO_ACCURATE_DIFF BASIC_CFLAGS += -DNO_ACCURATE_DIFF endif +ifdef NO_PERL_MAKEMAKER + export NO_PERL_MAKEMAKER +endif # Shell quote (do not use $(call) to accommodate ancient setups); @@ -569,8 +579,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 @@ -603,7 +613,11 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.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` && \ @@ -798,7 +812,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 + $(MAKE) -C perl prefix='$(prefix_SQ)' install if test 'z$(bindir_SQ)' != 'z$(gitexecdir_SQ)'; \ then \ ln -f '$(DESTDIR_SQ)$(bindir_SQ)/git$X' \ @@ -868,8 +882,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 diff --git a/builtin-mv.c b/builtin-mv.c index 54dd3bfe8a..d14a4a7f5c 100644 --- a/builtin-mv.c +++ b/builtin-mv.c @@ -146,21 +146,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-shortlog.c b/builtin-shortlog.c index f1124e261b..3fc43dd7dd 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -188,18 +188,25 @@ static void read_from_stdin(struct path_list *list) bob = buffer + strlen(buffer); else { offset = 8; - while (isspace(bob[-1])) + 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)) + 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, strlen(buffer2)); + buffer2 + i, l2 - i); + } } } } @@ -236,7 +243,7 @@ static void get_from_rev(struct rev_info *rev, struct path_list *list) author = scratch; authorlen = strlen(scratch); } else { - while (bracket[-1] == ' ') + if (bracket[-1] == ' ') bracket--; author = buffer + 7; diff --git a/fetch.c b/fetch.c index c426c04997..663b4b2f42 100644 --- a/fetch.c +++ b/fetch.c @@ -22,14 +22,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 +178,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/git-clone.sh b/git-clone.sh index d4ee93f75b..0ace989fde 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -14,7 +14,7 @@ die() { } usage() { - die "Usage: $0 [--template=] [--use-immingled-remote] [--reference ] [--bare] [-l [-s]] [-q] [-u ] [--origin ] [-n] []" + die "Usage: $0 [--template=] [--no-separate-remote] [--reference ] [--bare] [-l [-s]] [-q] [-u ] [--origin ] [-n] []" } get_repo_base() { @@ -140,7 +140,7 @@ while *,--use-separate-remote) # default use_separate_remote=t ;; - *,--use-immingled-remote) + *,--no-separate-remote) use_separate_remote= ;; 1,--reference) usage ;; *,--reference) @@ -176,7 +176,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" @@ -377,9 +377,9 @@ then *) 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" && + git-repo-config remote."$origin".url "$repo" && + git-repo-config remote."$origin".fetch \ + "refs/heads/$head_points_at:$origin_track" && (cd "$GIT_DIR/$remote_top" && find . -type f -print) | while read dotslref do @@ -393,8 +393,8 @@ Pull: refs/heads/$head_points_at:$origin_track" && then continue fi - echo "Pull: refs/heads/${name}:$remote_top/${name}" - done >>"$GIT_DIR/remotes/$origin" && + git-repo-config remote."$origin".fetch "refs/heads/${name}:$remote_top/${name}" '^$' + done && case "$use_separate_remote" in t) rm -f "refs/remotes/$origin/HEAD" diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index 7bac16e946..c9d1d88f2e 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -116,6 +116,7 @@ close MSG; my (@afiles, @dfiles, @mfiles, @dirs); +my %amodes; my @files = safe_pipe_capture('git-diff-tree', '-r', $parent, $commit); #print @files; $? && die "Error in git-diff-tree"; @@ -124,6 +125,7 @@ my @fields = split(m!\s+!, $f); if ($fields[4] eq 'A') { my $path = $fields[5]; + $amodes{$path} = $fields[1]; push @afiles, $path; # add any needed parent directories $path = dirname $path; @@ -268,6 +270,7 @@ } foreach my $f (@afiles) { + set_new_file_permissions($f, $amodes{$f}); if (grep { $_ eq $f } @bfiles) { system('cvs', 'add','-kb',$f); } else { @@ -342,3 +345,13 @@ sub safe_pipe_capture { } return wantarray ? @output : join('',@output); } + +# For any file we want to add to cvs, we must first set its permissions +# properly, *before* the "cvs add ..." command. Otherwise, it is impossible +# to change the permission of the file in the CVS repository using only cvs +# commands. This should be fixed in cvs-1.12.14. +sub set_new_file_permissions { + my ($file, $perm) = @_; + chmod oct($perm), $file + or die "failed to set permissions of \"$file\": $!\n"; +} diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 55ff83b1a3..2a8447e253 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/; diff --git a/git-merge.sh b/git-merge.sh index 272f004622..a948878b91 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -188,8 +188,9 @@ else # in this loop. merge_name=$(for remote do - rh=$(git-rev-parse --verify "$remote"^0 2>/dev/null) && - bh=$(git show-ref -s --verify "refs/heads/$remote") && + rh=$(git-rev-parse --verify "$remote"^0 2>/dev/null) || + continue ;# not something we can merge + bh=$(git show-ref -s --verify "refs/heads/$remote" 2>/dev/null) if test "$rh" = "$bh" then echo "$rh branch '$remote' of ." diff --git a/git-parse-remote.sh b/git-parse-remote.sh index 19bc3857d1..6ae534bf89 100755 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -116,7 +116,7 @@ expand_refs_wildcard () { while read sha1 name do mapped=${name#"$from"} - if test "z$name" != "z${name#'^{}'}" || + if test "z$name" != "z${name%'^{}'}" || test "z$name" = "z$mapped" then continue @@ -134,6 +134,8 @@ 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= + found_mergeref= + curr_branch= if test "$1" = "-d" then shift ; remote="$1" ; shift @@ -171,6 +173,10 @@ canon_refs_list_for_fetch () { dot_prefix= && break done fi + if test -z $dot_prefix + then + found_mergeref=true + fi case "$remote" in '') remote=HEAD ;; refs/heads/* | refs/tags/* | refs/remotes/*) ;; @@ -191,6 +197,11 @@ canon_refs_list_for_fetch () { fi echo "${dot_prefix}${force}${remote}:${local}" done + if test -z "$found_mergeref" -a "$curr_branch" + then + echo >&2 "Warning: No merge candidate found because value of config option + \"branch.${curr_branch}.merge\" does not match any remote branch fetched." + fi } # Returns list of src: (no store), or src:dst (store) 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-reset.sh b/git-reset.sh index 3133b5bd25..c0feb4435d 100755 --- a/git-reset.sh +++ b/git-reset.sh @@ -63,6 +63,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-svn.perl b/git-svn.perl index d0bd0bdeb8..15254e4795 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -21,7 +21,17 @@ $ENV{LC_ALL} = 'C'; $| = 1; # unbuffer STDOUT -sub fatal (@) { print STDERR $@; exit 1 } +# 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 } # 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 @@ -72,7 +82,7 @@ sub nag_lib { $_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_co_url_revs, $_svn_pg_peg_revs, $_svn_can_do_switch); my @repo_path_split_cache; my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext, @@ -459,6 +469,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 }; @@ -593,8 +604,9 @@ 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")); + chomp(my @refs = safe_qx(qw/git-rev-list --no-merges/, "$gs..$head")); my $last_rev; foreach my $d (reverse @refs) { if (quiet_run('git-rev-parse','--verify',"$d~1") != 0) { @@ -621,16 +633,16 @@ sub dcommit { } return if $_dry_run; fetch(); - my @diff = safe_qx(qw/git-diff-tree HEAD/, $gs); + my @diff = safe_qx('git-diff-tree', $head, $gs); my @finish; if (@diff) { @finish = qw/rebase/; push @finish, qw/--merge/ if $_merge; push @finish, "--strategy=$_strategy" if $_strategy; - print STDERR "W: HEAD and $gs differ, using @finish:\n", @diff; + 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"; + print "No changes between current $head and $gs\n", + "Resetting to the latest $gs\n"; @finish = qw/reset --mixed/; } sys('git', @finish, $gs); @@ -2015,9 +2027,17 @@ sub git_commit { # 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") }) { + open my $null, '>', '/dev/null' or croak $!; + open my $stderr, '>&', \*STDERR or croak $!; + open STDERR, '>&', $null or croak $!; + if (my $cur = eval { safe_qx('git-rev-parse', + "refs/remotes/$GIT_SVN^0") }) { + chomp $cur; push @tmp_parents, $cur; } + open STDERR, '>&', $stderr or croak $!; + close $stderr or croak $!; + close $null or croak $!; if (exists $tree_map{$tree}) { foreach my $p (@{$tree_map{$tree}}) { @@ -2876,6 +2896,24 @@ sub libsvn_connect { return $ra; } +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 @@ -2883,7 +2921,7 @@ sub libsvn_dup_ra { } sub libsvn_get_file { - my ($gui, $f, $rev, $chg) = @_; + my ($gui, $f, $rev, $chg, $untracked) = @_; $f =~ s#^/##; print "\t$chg\t$f\n" unless $_q; @@ -2921,11 +2959,25 @@ sub libsvn_get_file { waitpid $pid, 0; $hash =~ /^$sha1$/o or die "not a sha1: $hash\n"; } + %{$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"; @@ -2933,8 +2985,65 @@ sub libsvn_log_entry { 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 { @@ -2953,9 +3062,11 @@ sub process_rm { } print "\tD\t$f/\n" unless $q; close $ls or croak $?; + 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; } } @@ -2976,13 +3087,14 @@ sub libsvn_fetch_delta { unless ($ed->{git_commit_ok}) { die "SVN connection failed somewhere...\n"; } - libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]); + 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 $ut = { empty => {}, dir_prop => {}, file_prop => {} }; my $p = $SVN->{svn_path}; foreach my $f (keys %$paths) { my $m = $paths->{$f}->action(); @@ -2993,8 +3105,11 @@ sub libsvn_fetch_full { $f =~ s#^/##; } if ($m =~ /^[DR]$/) { - process_rm($gui, $last_commit, $f, $_q); - 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; @@ -3007,18 +3122,32 @@ sub libsvn_fetch_full { } } elsif ($t == $SVN::Node::dir && $m =~ /^[AR]$/) { my @traversed = (); - libsvn_traverse($gui, '', $f, $rev, \@traversed); - foreach (@traversed) { - $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 (keys %amr) { - libsvn_get_file($gui, $_, $rev, $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]); + libsvn_log_entry($rev, $author, $date, $msg, [$last_commit], $ut); } sub svn_grab_base_rev { @@ -3079,25 +3208,38 @@ sub libsvn_parse_revision { } sub libsvn_traverse { - my ($gui, $pfx, $path, $rev, $files) = @_; + my ($gui, $pfx, $path, $rev, $files, $untracked) = @_; my $cwd = length $pfx ? "$pfx/$path" : $path; my $pool = SVN::Pool->new; $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 { @@ -3197,12 +3339,26 @@ sub libsvn_find_parent_branch { unlink $GIT_SVN_INDEX; print STDERR "Found branch parent: ($GIT_SVN) $parent\n"; sys(qw/git-read-tree/, $parent); - # I can't seem to get do_switch() to work correctly with - # the SWIG interface (TypeError when passing switch_url...), - # so we'll unconditionally bypass the delta interface here - # for now - return libsvn_fetch_full($parent, $paths, $rev, - $author, $date, $msg); + 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; @@ -3222,6 +3378,7 @@ sub libsvn_new_tree { return $log_entry; } my ($paths, $rev, $author, $date, $msg) = @_; + my $ut; if ($_xfer_delta) { my $pool = SVN::Pool->new; my $ed = SVN::Git::Fetcher->new({q => $_q}); @@ -3233,12 +3390,14 @@ sub libsvn_new_tree { unless ($ed->{git_commit_ok}) { die "SVN connection failed somewhere...\n"; } + $ut = $ed; } else { + $ut = { empty => {}, dir_prop => {}, file_prop => {} }; open my $gui, '| git-update-index -z --index-info' or croak $!; - libsvn_traverse($gui, '', $SVN->{svn_path}, $rev); + libsvn_traverse($gui, '', $SVN->{svn_path}, $rev, undef, $ut); close $gui or croak $?; } - return libsvn_log_entry($rev, $author, $date, $msg); + libsvn_log_entry($rev, $author, $date, $msg, [], $ut); } sub find_graft_path_commit { @@ -3423,13 +3582,28 @@ sub new { $self->{gui} = $gui; $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} = {}; 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) = @_; - process_rm($self->{gui}, $self->{c}, $path, $self->{q}); + my $t = process_rm($self->{gui}, $self->{c}, $path, $self->{q}); + $self->{empty}->{$path} = 0 if $t == $SVN::Node::dir; undef; } @@ -3437,16 +3611,50 @@ sub open_file { my ($self, $path, $pb, $rev) = @_; my ($mode, $blob) = (safe_qx('git-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') { @@ -3455,6 +3663,9 @@ sub change_file_prop { } } 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; } diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 093bd72058..5ea3fda540 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -120,7 +120,7 @@ # 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, @@ -3229,10 +3229,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))) { @@ -3269,13 +3272,24 @@ sub git_blob { } git_print_page_path($file_name, "blob", $hash_base); print "
\n"; - my $nr; - while (my $line = <$fd>) { - chomp $line; - $nr++; - $line = untabify($line); - printf "
%4i %s
\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 "
%4i %s
\n", + $nr, $nr, $nr, esc_html($line, -nbsp=>1); + } + } elsif ($mimetype =~ m!^image/!) { + print qq!$file_name$hash, + hash_base=>$hash_base, file_name=>$file_name) . + qq!" />\n!; } close $fd or print "Reading blob failed.\n"; @@ -4282,7 +4296,7 @@ sub git_feed { } if (defined $logo_url) { # not twice as wide as tall: 72 x 27 pixels - print "" . esc_url($logo_url) . "\n"; + print "" . esc_url($logo) . "\n"; } if (! %latest_date) { # dummy date to keep the feed valid until commits trickle in: diff --git a/index-pack.c b/index-pack.c index 8331d99a62..6d6c92bf14 100644 --- a/index-pack.c +++ b/index-pack.c @@ -96,7 +96,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; } } diff --git a/merge-recursive.c b/merge-recursive.c index 6e13b8ed5b..9d53bcd5d6 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1222,7 +1222,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"); } 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/receive-pack.c b/receive-pack.c index 1a141dc1e5..e76d9aea31 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -11,7 +11,7 @@ static const char receive_pack_usage[] = "git-receive-pack "; 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 delete-refs "; @@ -120,7 +120,8 @@ static int update(struct command *cmd) "but I can't find it!", new_hex); } if (deny_non_fast_forwards && !is_null_sha1(new_sha1) && - !is_null_sha1(old_sha1)) { + !is_null_sha1(old_sha1) && + !strncmp(name, "refs/heads/", 11)) { struct commit *old_commit, *new_commit; struct commit_list *bases, *ent; 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/t6024-recursive-merge.sh b/t/t6024-recursive-merge.sh new file mode 100755 index 0000000000..9416c271eb --- /dev/null +++ b/t/t6024-recursive-merge.sh @@ -0,0 +1,70 @@ +#!/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" + +git ls-files --stage > out +cat > expect << EOF +100644 f70f10e4db19068f79bc43844b49f3eece45c4e8 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/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh index 6e566d4409..c1024790e4 100755 --- a/t/t9200-git-cvsexportcommit.sh +++ b/t/t9200-git-cvsexportcommit.sh @@ -142,4 +142,20 @@ test_expect_success \ diff F/newfile6.png ../F/newfile6.png )' +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/unpack-trees.c b/unpack-trees.c index 7cfd628d8e..47aa804a86 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -370,7 +370,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 +381,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 *)); 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) ' '; }