diff options
40 files changed, 651 insertions, 191 deletions
diff --git a/Documentation/cvs-migration.txt b/Documentation/cvs-migration.txt index 6812683a16..a436180dd4 100644 --- a/Documentation/cvs-migration.txt +++ b/Documentation/cvs-migration.txt @@ -24,6 +24,11 @@ First, note some ways that git differs from CVS: single shared repository which people can synchronize with; see below for details. + * Since every working tree contains a repository, a commit in your + private repository will not publish your changes; it will only create + a revision. You have to "push" your changes to a public repository to + make them visible to others. + Importing a CVS archive ----------------------- @@ -76,8 +81,8 @@ 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 ------------------------------------ +Creating a Shared Repository +---------------------------- Start with an ordinary git working directory containing the project, and remove the checked-out files, keeping just the bare .git directory: @@ -105,7 +110,10 @@ $ 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 +Performing Development on a Shared Repository +--------------------------------------------- + +Suppose a 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: @@ -134,15 +142,17 @@ Pull: master:origin ------------ ================================ -You can update the shared repository with your changes using: +You can update the shared repository with your changes by first commiting +your changes, and then 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. +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 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..d5efa00dea 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -11,7 +11,7 @@ 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> + [--use-separate-remote | --no-separate-remote] <repository> [<directory>] DESCRIPTION @@ -105,7 +105,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-diff.txt b/Documentation/git-diff.txt index 228c4d95bd..3144864d85 100644 --- a/Documentation/git-diff.txt +++ b/Documentation/git-diff.txt @@ -22,8 +22,10 @@ the number of trees given to the command. * 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 + `--index` can be given to compare the index file and the named tree. + `--cached` is a deprecated alias for `--index`. It's use is + discouraged. * When two <tree-ish>s are given, these two trees are compared using `git-diff-tree`. @@ -47,7 +49,7 @@ Various ways to check your working tree:: + ------------ $ git diff <1> -$ git diff --cached <2> +$ git diff --index <2> $ git diff HEAD <3> ------------ + diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt index 8199615dde..5bede9ac22 100644 --- a/Documentation/git-repo-config.txt +++ b/Documentation/git-repo-config.txt @@ -77,6 +77,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 ----------- diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index a764d1f8ee..a45067e164 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. @@ -274,7 +274,7 @@ ADVANCED OPTIONS -b<refname>:: --branch <refname>:: -Used with 'fetch' or 'commit'. +Used with 'fetch', 'dcommit' or 'commit'. This can be used to join arbitrary git branches to remotes/git-svn on new commits where the tree object is equivalent. @@ -368,7 +368,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 +377,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: @@ -404,26 +403,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 +483,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-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/tutorial.txt b/Documentation/tutorial.txt index 35af81a3de..fe4491de41 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 ----------------------- @@ -31,7 +43,8 @@ defaulting to local storage area 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) @@ -75,7 +94,7 @@ $ git add path/to/new/file ------------------------------------------------ then commit as usual. No special command is required when removing a -file; just remove it, then commit. +file; just remove it, then tell `commit` about the file as usual. At any point you can view the history of your changes using @@ -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 @@ -539,6 +543,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); @@ -568,8 +575,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 @@ -602,7 +609,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` && \ @@ -796,7 +807,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' \ @@ -866,8 +877,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-blame.c b/builtin-blame.c index 066dee743e..dc3ffeaff8 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -1435,14 +1435,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; @@ -1787,6 +1787,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-diff.c b/builtin-diff.c index a6590205e8..1c535b1dd6 100644 --- a/builtin-diff.c +++ b/builtin-diff.c @@ -137,7 +137,7 @@ static int builtin_diff_index(struct rev_info *revs, int cached = 0; while (1 < argc) { const char *arg = argv[1]; - if (!strcmp(arg, "--cached")) + if (!strcmp(arg, "--index") || !strcmp(arg, "--cached")) cached = 1; else usage(builtin_diff_usage); diff --git a/builtin-ls-files.c b/builtin-ls-files.c index ad8c41e731..bc79ce40fc 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -487,10 +487,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-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-pack-objects.c b/builtin-pack-objects.c index 753bcd57b0..a2dc7d1d9d 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -514,6 +514,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); } @@ -1662,7 +1664,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-shortlog.c b/builtin-shortlog.c index b5b13dee3b..f1124e261b 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -298,9 +298,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) if (!access(".mailmap", R_OK)) read_mailmap(".mailmap"); - if (rev.pending.nr == 1) - die ("Need a range!"); - else if (rev.pending.nr == 0) + if (rev.pending.nr == 0) read_from_stdin(&list); else get_from_rev(&rev, &list); diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index d8ae4d7886..447ec20467 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -34,7 +34,19 @@ __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 () @@ -51,7 +63,7 @@ __git_ps1 () __git_heads () { - local cmd i is_hash=y dir="${1:-$(__gitdir)}" + 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)' \ @@ -60,7 +72,7 @@ __git_heads () done return fi - for i in $(git-ls-remote "$dir" 2>/dev/null); do + for i in $(git-ls-remote "$1" 2>/dev/null); do case "$is_hash,$i" in y,*) is_hash=n ;; n,*^{}) is_hash=y ;; @@ -72,7 +84,7 @@ __git_heads () __git_refs () { - local cmd i is_hash=y dir="${1:-$(__gitdir)}" + local cmd i is_hash=y dir="$(__gitdir "$1")" if [ -d "$dir" ]; then if [ -e "$dir/HEAD" ]; then echo HEAD; fi for i in $(git --git-dir="$dir" \ @@ -101,20 +113,9 @@ __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 - case "$is_hash,$i" in - 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" ;; - esac + local i + for i in $(__git_refs "$1"); do + echo "$i:$i" done } @@ -398,6 +399,20 @@ _git_cherry_pick () 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 @@ -768,6 +783,7 @@ _git () 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 ;; @@ -804,6 +820,7 @@ 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 diff --git a/fetch-pack.c b/fetch-pack.c index 0a169dce85..743eab7efa 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -566,6 +566,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; @@ -617,6 +640,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]); diff --git a/git-clone.sh b/git-clone.sh index b2d0f08e67..0ace989fde 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>] [-n] <repo> [<dir>]" + die "Usage: $0 [--template=<template_directory>] [--no-separate-remote] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [-n] <repo> [<dir>]" } 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" 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 @@ if ($opt_a) { 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 @@ foreach my $f (@files) { 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 @@ if (($? >> 8) == 2) { } 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-fetch.sh b/git-fetch.sh index 4425562098..4eecf148ea 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -88,6 +88,10 @@ then : >"$GIT_DIR/FETCH_HEAD" fi +# Global that is reused later +ls_remote_result=$(git ls-remote $upload_pack "$remote") || + die "Cannot find the reflist at $remote" + append_fetch_head () { head_="$1" remote_="$2" @@ -233,10 +237,7 @@ reflist=$(get_remote_refs_for_fetch "$@") if test "$tags" then taglist=`IFS=" " && - ( - git-ls-remote $upload_pack --tags "$remote" || - echo fail ouch - ) | + echo "$ls_remote_result" | while read sha1 name do case "$sha1" in @@ -245,6 +246,8 @@ then esac case "$name" in *^*) continue ;; + refs/tags/*) ;; + *) continue ;; esac if git-check-ref-format "$name" then @@ -304,22 +307,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 ;; @@ -432,7 +433,7 @@ case "$no_tags$tags" in # effective only when we are following remote branch # using local tracking branch. taglist=$(IFS=" " && - git-ls-remote $upload_pack --tags "$remote" | + echo "$ls_remote_result" | sed -n -e 's|^\('"$_x40"'\) \(refs/tags/.*\)^{}$|\1 \2|p' \ -e 's|^\('"$_x40"'\) \(refs/tags/.*\)$|\1 \2|p' | while read sha1 name diff --git a/git-merge.sh b/git-merge.sh index 75af10d3e4..272f004622 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -189,13 +189,13 @@ else merge_name=$(for remote do rh=$(git-rev-parse --verify "$remote"^0 2>/dev/null) && - if git show-ref -q --verify "refs/heads/$remote" + bh=$(git show-ref -s --verify "refs/heads/$remote") && + if test "$rh" = "$bh" then - what=branch + echo "$rh branch '$remote' of ." else - what=commit - fi && - echo "$rh $what '$remote'" + echo "$rh commit '$remote'" + fi done | git-fmt-merge-msg ) merge_msg="${merge_msg:+$merge_msg$LF$LF}$merge_name" diff --git a/git-parse-remote.sh b/git-parse-remote.sh index c325ef761e..da064a53f6 100755 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -90,6 +90,43 @@ 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" | + ( + IFS=' ' + while read sha1 name + do + mapped=${name#"$from"} + if test "z$name" != "z${name%'^{}'}" || + test "z$name" = "z$mapped" + then + continue + fi + 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 @@ -107,6 +144,8 @@ canon_refs_list_for_fetch () { merge_branches=$(git-repo-config \ --get-all "branch.${curr_branch}.merge") fi + set x $(expand_refs_wildcard "$@") + shift fi for ref do 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 d5d9c49fd6..d0bd0bdeb8 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -60,6 +60,7 @@ nag_lib() unless $_use_lib; 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, @@ -68,7 +69,8 @@ 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 @repo_path_split_cache; @@ -122,7 +124,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 ], @@ -135,6 +142,8 @@ 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, @@ -759,16 +768,17 @@ sub show_log { } } + config_pager(); 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(); + 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}; @@ -777,25 +787,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/) { + } 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}}, $_; } } @@ -901,12 +911,30 @@ sub cmt_showable { return defined $c->{r}; } +sub log_use_color { + return 1 if $_color; + my $dc; + chomp($dc = `git-repo-config --get diff.color`); + if ($dc eq 'auto') { + if (-t *STDOUT || (defined $_pager && + `git-repo-config --bool --get pager.color` !~ /^false/)) { + 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 diff.color`); + $dc eq 'true'; +} + sub git_svn_log_cmd { my ($r_min, $r_max) = @_; my @cmd = (qw/git-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'; @@ -1152,7 +1180,7 @@ sub graft_file_copy_lib { while (1) { my $pool = SVN::Pool->new; libsvn_get_log(libsvn_dup_ra($SVN), [$path], - $min, $max, 0, 1, 1, + $min, $max, 0, 2, 1, sub { libsvn_graft_file_copies($grafts, $tree_paths, $path, @_); @@ -2533,14 +2561,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) { @@ -2548,8 +2580,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 { @@ -2675,6 +2707,9 @@ sub libsvn_load { require SVN::Ra; require SVN::Delta; push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor'; + push @SVN::Git::Fetcher::ISA, 'SVN::Delta::Editor'; + *SVN::Git::Fetcher::process_rm = *process_rm; + *SVN::Git::Fetcher::safe_qx = *safe_qx; my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file. $SVN::Node::dir.$SVN::Node::unknown. $SVN::Node::none.$SVN::Node::file. @@ -2827,6 +2862,13 @@ sub libsvn_connect { 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/*##; @@ -2896,7 +2938,7 @@ sub libsvn_log_entry { } sub process_rm { - my ($gui, $last_commit, $f) = @_; + 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 $!; @@ -2907,17 +2949,40 @@ sub process_rm { local $/ = "\0"; while (<$ls>) { print $gui '0 ',0 x 40,"\t",$_ or croak $!; + print "\tD\t$_\n" unless $q; } + print "\tD\t$f/\n" unless $q; close $ls or croak $?; } else { print $gui '0 ',0 x 40,"\t",$f,"\0" or croak $!; + print "\tD\t$f\n" unless $q; } } 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]); +} + +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 %amr; my $p = $SVN->{svn_path}; foreach my $f (keys %$paths) { my $m = $paths->{$f}->action(); @@ -2928,8 +2993,7 @@ sub libsvn_fetch { $f =~ s#^/##; } if ($m =~ /^[DR]$/) { - print "\t$m\t$f\n" unless $_q; - process_rm($gui, $last_commit, $f); + process_rm($gui, $last_commit, $f, $_q); next if $m eq 'D'; # 'R' can be file replacements, too, right? } @@ -2937,7 +3001,7 @@ sub libsvn_fetch { 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"; } @@ -2945,13 +3009,13 @@ sub libsvn_fetch { my @traversed = (); libsvn_traverse($gui, '', $f, $rev, \@traversed); foreach (@traversed) { - push @amr, [ $m, $_ ] + $amr{$_} = $m; } } $pool->clear; } - foreach (@amr) { - libsvn_get_file($gui, $_->[1], $rev, $_->[0]); + foreach (keys %amr) { + libsvn_get_file($gui, $_, $rev, $amr{$_}); } close $gui or croak $?; return libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]); @@ -3070,7 +3134,7 @@ sub revisions_eq { # 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); + 0, 0, 1, sub {$nr++}, $pool); $pool->clear; } else { my ($url, undef) = repo_path_split($SVN_URL); @@ -3133,7 +3197,11 @@ sub libsvn_find_parent_branch { 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, + # 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); } print STDERR "Nope, branch point not imported or unknown\n"; @@ -3142,6 +3210,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); } @@ -3153,9 +3222,22 @@ 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->{svn_path}, $rev); - close $gui or croak $?; + 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"; + } + } else { + open my $gui, '| git-update-index -z --index-info' or croak $!; + libsvn_traverse($gui, '', $SVN->{svn_path}, $rev); + close $gui or croak $?; + } return libsvn_log_entry($rev, $author, $date, $msg); } @@ -3239,11 +3321,11 @@ sub libsvn_commit_cb { sub libsvn_ls_fullurl { my $fullurl = shift; - $SVN ||= libsvn_connect($fullurl); + my $ra = libsvn_connect($fullurl); my @ret; my $pool = SVN::Pool->new; - my ($dirent, undef, undef) = $SVN->get_dir($SVN->{svn_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 @@ -3325,6 +3407,142 @@ sub copy_remote_ref { "refs/remotes/$GIT_SVN on $origin\n"; } } +package SVN::Git::Fetcher; +use vars qw/@ISA/; +use strict; +use warnings; +use Carp qw/croak/; +use IO::File qw//; + +# 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; + open my $gui, '| git-update-index -z --index-info' or croak $!; + $self->{gui} = $gui; + $self->{c} = $git_svn->{c} if exists $git_svn->{c}; + $self->{q} = $git_svn->{q}; + require Digest::MD5; + $self; +} + +sub delete_entry { + my ($self, $path, $rev, $pb) = @_; + process_rm($self->{gui}, $self->{c}, $path, $self->{q}); + undef; +} + +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/); + { 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) = @_; + { path => $path, mode_a => 100644, mode_b => 100644, + pool => SVN::Pool->new, action => 'A' }; +} + +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; + } + 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; + close $self->{gui}; + $self->SUPER::abort_edit(@_); +} + +sub close_edit { + my $self = shift; + close $self->{gui} or croak $!; + $self->{git_commit_ok} = 1; + $self->SUPER::close_edit(@_); +} package SVN::Git::Editor; use vars qw/@ISA/; @@ -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 093bd72058..ffe8ce13ff 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -4282,7 +4282,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: @@ -158,12 +158,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) 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..bd483b0997 --- /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)' $< FIRST_MAKEFILE='$@' 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/receive-pack.c b/receive-pack.c index 1a141dc1e5..a20bc924d6 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -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/Makefile b/t/Makefile index e1c6c92d9c..c9bd9a4690 100644 --- a/t/Makefile +++ b/t/Makefile @@ -23,8 +23,9 @@ clean: # we can test NO_OPTIMIZE_COMMITS independently of LC_ALL full-svn-test: + $(MAKE) $(TSVN) GIT_SVN_NO_LIB=0 GIT_SVN_DELTA_FETCH=1 \ + GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C $(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 \ diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index 29a1e72c61..63c670304f 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh @@ -45,6 +45,6 @@ else svnadmin create "$svnrepo" fi -svnrepo="file://$svnrepo/test-git-svn" +svnrepo="file://$svnrepo" 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/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..f9de232366 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -228,6 +228,11 @@ tree 56a30b966619b863674f5978696f4a3594f2fca9 tree d667270a1f7b109f5eb3aaea21ede14b56bfdd6e tree 8f51f74cf0163afc9ad68a4b1537288c4558b5a4 EOF + +if test -z "$GIT_SVN_NO_LIB" || test "$GIT_SVN_NO_LIB" -eq 0; then + echo tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 >> expected +fi + test_expect_success "$name" "diff -u a expected" test_done 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/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) ' '; } |