diff options
-rw-r--r-- | Documentation/git-branch.txt | 10 | ||||
-rw-r--r-- | Documentation/git-checkout.txt | 23 | ||||
-rw-r--r-- | Documentation/git-commit.txt | 4 | ||||
-rw-r--r-- | Documentation/git-cvsimport.txt | 27 | ||||
-rw-r--r-- | Documentation/git-grep.txt | 14 | ||||
-rw-r--r-- | Documentation/git-init-db.txt | 18 | ||||
-rw-r--r-- | Documentation/git-rebase.txt | 6 | ||||
-rw-r--r-- | Documentation/git-show-branch.txt | 2 | ||||
-rw-r--r-- | Documentation/git-svnimport.txt | 25 | ||||
-rw-r--r-- | Documentation/git-whatchanged.txt | 6 | ||||
-rw-r--r-- | Documentation/git.txt | 3 | ||||
-rw-r--r-- | Makefile | 11 | ||||
-rw-r--r-- | apply.c | 2 | ||||
-rw-r--r-- | blame.c | 2 | ||||
-rw-r--r-- | contrib/emacs/git.el | 51 | ||||
-rwxr-xr-x | contrib/git-svn/git-svn.perl | 14 | ||||
-rw-r--r-- | diff-delta.c | 17 | ||||
-rwxr-xr-x | generate-cmdlist.sh | 10 | ||||
-rwxr-xr-x | git-commit.sh | 2 | ||||
-rwxr-xr-x | git-fetch.sh | 1 | ||||
-rwxr-xr-x | git-pull.sh | 16 | ||||
-rwxr-xr-x | git-send-email.perl | 11 | ||||
-rw-r--r-- | http-fetch.c | 8 | ||||
-rw-r--r-- | http-push.c | 241 | ||||
-rw-r--r-- | ls-files.c | 3 | ||||
-rw-r--r-- | revision.c | 12 | ||||
-rw-r--r-- | sha1_file.c | 18 |
27 files changed, 448 insertions, 109 deletions
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 4cd0cb90ad..71ecd858aa 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -7,16 +7,20 @@ git-branch - Create a new branch, or remove an old one SYNOPSIS -------- -'git-branch' [(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]] +[verse] +'git-branch' [[-f] <branchname> [<start-point>]] +'git-branch' (-d | -D) <branchname> DESCRIPTION ----------- If no argument is provided, show available branches and mark current branch with star. Otherwise, create a new branch of name <branchname>. - If a starting point is also specified, that will be where the branch is created, otherwise it will be created at the current HEAD. +With a `-d` or `-D` option, `<branchname>` will be deleted. + + OPTIONS ------- -d:: @@ -39,7 +43,7 @@ OPTIONS Examples ~~~~~~~~ -Start development off of a know tag:: +Start development off of a known tag:: + ------------ $ git clone git://git.kernel.org/pub/scm/.../linux-2.6 my2.6 diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 556e733c9b..985bb2f827 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -7,15 +7,18 @@ git-checkout - Checkout and switch to a branch SYNOPSIS -------- -'git-checkout' [-f] [-b <new_branch>] [-m] [<branch>] [<paths>...] +[verse] +'git-checkout' [-f] [-b <new_branch>] [-m] [<branch>] +'git-checkout' [-m] [<branch>] <paths>... DESCRIPTION ----------- -When <paths> are not given, this command switches branches, by +When <paths> are not given, this command switches branches by updating the index and working tree to reflect the specified branch, <branch>, and updating HEAD to be <branch> or, if -specified, <new_branch>. +specified, <new_branch>. Using -b will cause <new_branch> to +be created. When <paths> are given, this command does *not* switch branches. It updates the named paths in the working tree from @@ -29,17 +32,17 @@ given paths before updating the working tree. OPTIONS ------- -f:: - Force an re-read of everything. + Force a re-read of everything. -b:: Create a new branch and start it at <branch>. -m:: - If you have local modifications to a file that is - different between the current branch and the branch you - are switching to, the command refuses to switch - branches, to preserve your modifications in context. - With this option, a three-way merge between the current + If you have local modifications to one or more files that + are different between the current branch and the branch to + which you are switching, the command refuses to switch + branches in order to preserve your modifications in context. + However, with this option, a three-way merge between the current branch, your working tree contents, and the new branch is done, and you will be on the new branch. + @@ -82,7 +85,7 @@ $ git checkout -- hello.c ------------ . After working in a wrong branch, switching to the correct -branch you would want to is done with: +branch would be done using: + ------------ $ git checkout mytopic diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 214ed235c5..d04b342a95 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -18,6 +18,10 @@ Updates the index file for given paths, or all modified files if VISUAL and EDITOR environment variables to edit the commit log message. +Several environment variable are used during commits. They are +documented in gitlink:git-commit-tree[1]. + + This command can run `commit-msg`, `pre-commit`, and `post-commit` hooks. See link:hooks.html[hooks] for more information. diff --git a/Documentation/git-cvsimport.txt b/Documentation/git-cvsimport.txt index 57027b448f..b0c6d7c303 100644 --- a/Documentation/git-cvsimport.txt +++ b/Documentation/git-cvsimport.txt @@ -99,21 +99,24 @@ If you need to pass multiple options, separate them with a comma. CVS by default uses the unix username when writing its commit logs. Using this option and an author-conv-file in this format - ++ +--------- exon=Andreas Ericsson <ae@op5.se> spawn=Simon Pawn <spawn@frog-pond.org> - git-cvsimport will make it appear as those authors had - their GIT_AUTHOR_NAME and GIT_AUTHOR_EMAIL set properly - all along. - - For convenience, this data is saved to $GIT_DIR/cvs-authors - each time the -A option is provided and read from that same - file each time git-cvsimport is run. - - It is not recommended to use this feature if you intend to - export changes back to CVS again later with - git-link[1]::git-cvsexportcommit. +--------- ++ +git-cvsimport will make it appear as those authors had +their GIT_AUTHOR_NAME and GIT_AUTHOR_EMAIL set properly +all along. ++ +For convenience, this data is saved to $GIT_DIR/cvs-authors +each time the -A option is provided and read from that same +file each time git-cvsimport is run. ++ +It is not recommended to use this feature if you intend to +export changes back to CVS again later with +git-link[1]::git-cvsexportcommit. OUTPUT ------ diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index fbd2394481..d55456ae93 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -24,13 +24,13 @@ OPTIONS <option>...:: Either an option to pass to `grep` or `git-ls-files`. - - The following are the specific `git-ls-files` options - that may be given: `-o`, `--cached`, `--deleted`, `--others`, - `--killed`, `--ignored`, `--modified`, `--exclude=*`, - `--exclude-from=*`, and `--exclude-per-directory=*`. - - All other options will be passed to `grep`. ++ +The following are the specific `git-ls-files` options +that may be given: `-o`, `--cached`, `--deleted`, `--others`, +`--killed`, `--ignored`, `--modified`, `--exclude=\*`, +`--exclude-from=\*`, and `--exclude-per-directory=\*`. ++ +All other options will be passed to `grep`. <pattern>:: The pattern to look for. The first non option is taken diff --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt index ea4d849aa3..aeb1115af9 100644 --- a/Documentation/git-init-db.txt +++ b/Documentation/git-init-db.txt @@ -14,7 +14,8 @@ SYNOPSIS OPTIONS ------- --template=<template_directory>:: - Provide the directory in from which templates will be used. + Provide the directory from which templates will be used. + The default template directory is `/usr/share/git-core/templates`. --shared:: Specify that the git repository is to be shared amongst several users. @@ -22,9 +23,17 @@ OPTIONS DESCRIPTION ----------- -This simply creates an empty git repository - basically a `.git` directory -and `.git/object/??/`, `.git/refs/heads` and `.git/refs/tags` directories, -and links `.git/HEAD` symbolically to `.git/refs/heads/master`. +This command creates an empty git repository - basically a `.git` directory +with subdirectories for `objects`, `refs/heads`, `refs/tags`, and +templated files. +An initial `HEAD` file that references the HEAD of the master branch +is also created. + +If `--template=<template_directory>` is specified, `<template_directory>` +is used as the source of the template files rather than the default. +The template files include some directory structure, some suggested +"exclude patterns", and copies of non-executing "hook" files. The +suggested patterns and hook files are all modifiable and extensible. If the `$GIT_DIR` environment variable is set then it specifies a path to use instead of `./.git` for the base of the repository. @@ -38,7 +47,6 @@ repository. When specifying `--shared` the config variable "core.sharedRepositor is set to 'true' so that directories under `$GIT_DIR` are made group writable (and g+sx, since the git group may be not the primary group of all users). - Running `git-init-db` in an existing repository is safe. It will not overwrite things that are already there. The primary reason for rerunning `git-init-db` is to pick up newly added templates. diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 4d5b546db1..b36276c7ed 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -25,7 +25,7 @@ Assume the following history exists and the current branch is "topic": / D---E---F---G master -From this point, the result of the following commands: +From this point, the result of either of the following commands: git-rebase master git-rebase master topic @@ -36,7 +36,7 @@ would be: / D---E---F---G master -While, starting from the same point, the result of the following +While, starting from the same point, the result of either of the following commands: git-rebase --onto master~1 master @@ -58,7 +58,7 @@ OPTIONS <upstream>:: Upstream branch to compare against. -<head>:: +<branch>:: Working branch; defaults to HEAD. Author diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt index d3b6e620a8..f115b45ef6 100644 --- a/Documentation/git-show-branch.txt +++ b/Documentation/git-show-branch.txt @@ -141,7 +141,7 @@ it, having the following in the configuration file may help: ------------ -With this,`git show-branch` without extra parameters would show +With this, `git show-branch` without extra parameters would show only the primary branches. In addition, if you happen to be on your topic branch, it is shown as well. diff --git a/Documentation/git-svnimport.txt b/Documentation/git-svnimport.txt index 9d3865719c..b1b87c2fcd 100644 --- a/Documentation/git-svnimport.txt +++ b/Documentation/git-svnimport.txt @@ -75,18 +75,21 @@ When importing incrementally, you might need to edit the .git/svn2git file. -A <author_file>:: Read a file with lines on the form ++ +------ + username = User's Full Name <email@addr.es> - username = User's Full Name <email@addr.es> - - and use "User's Full Name <email@addr.es>" as the GIT - author and committer for Subversion commits made by - "username". If encountering a commit made by a user not in the - list, abort. - - For convenience, this data is saved to $GIT_DIR/svn-authors - each time the -A option is provided, and read from that same - file each time git-svnimport is run with an existing GIT - repository without -A. +------ ++ +and use "User's Full Name <email@addr.es>" as the GIT +author and committer for Subversion commits made by +"username". If encountering a commit made by a user not in the +list, abort. ++ +For convenience, this data is saved to $GIT_DIR/svn-authors +each time the -A option is provided, and read from that same +file each time git-svnimport is run with an existing GIT +repository without -A. -m:: Attempt to detect merges based on the commit message. This option diff --git a/Documentation/git-whatchanged.txt b/Documentation/git-whatchanged.txt index f02f939baa..641cb7ea97 100644 --- a/Documentation/git-whatchanged.txt +++ b/Documentation/git-whatchanged.txt @@ -47,9 +47,9 @@ OPTIONS By default, differences for merge commits are not shown. With this flag, show differences to that commit from all of its parents. - - However, it is not very useful in general, although it - *is* useful on a file-by-file basis. ++ +However, it is not very useful in general, although it +*is* useful on a file-by-file basis. Examples -------- diff --git a/Documentation/git.txt b/Documentation/git.txt index 8610d36c49..de3934d098 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -329,6 +329,9 @@ gitlink:git-revert[1]:: gitlink:git-shortlog[1]:: Summarizes 'git log' output. +gitlink:git-show[1]:: + Show one commit log and its diff. + gitlink:git-show-branch[1]:: Show branches and their commits. @@ -553,6 +553,13 @@ $(LIB_FILE): $(LIB_OBJS) doc: $(MAKE) -C Documentation all +TAGS: + rm -f TAGS + find . -name '*.[hcS]' -print | xargs etags -a + +tags: + rm -f tags + find . -name '*.[hcS]' -print | xargs ctags -a ### Testing rules @@ -617,7 +624,7 @@ rpm: dist clean: rm -f *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o $(LIB_FILE) rm -f $(ALL_PROGRAMS) git$X - rm -f *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h + rm -f *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags rm -rf $(GIT_TARNAME) rm -f $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz $(MAKE) -C Documentation/ clean @@ -626,5 +633,5 @@ clean: rm -f GIT-VERSION-FILE .PHONY: all install clean strip -.PHONY: .FORCE-GIT-VERSION-FILE +.PHONY: .FORCE-GIT-VERSION-FILE TAGS tags @@ -834,7 +834,7 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s patch->new_name = NULL; } - if (patch->is_new != !oldlines) + if (patch->is_new && oldlines) return error("new file depends on old contents"); if (patch->is_delete != !newlines) { if (newlines) @@ -748,7 +748,7 @@ int main(int argc, const char **argv) struct commit_info ci; const char *buf; int max_digits; - size_t longest_file, longest_author; + int longest_file, longest_author; int found_rename; const char* prefix = setup_git_directory(); diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index 5135e361be..ebd00ef9c4 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -59,14 +59,14 @@ (defcustom git-committer-name nil "User name to use for commits. -The default is to fall back to `add-log-full-name' and then `user-full-name'." +The default is to fall back to the repository config, then to `add-log-full-name' and then to `user-full-name'." :group 'git :type '(choice (const :tag "Default" nil) (string :tag "Name"))) (defcustom git-committer-email nil "Email address to use for commits. -The default is to fall back to `add-log-mailing-address' and then `user-mail-address'." +The default is to fall back to the git repository config, then to `add-log-mailing-address' and then to `user-mail-address'." :group 'git :type '(choice (const :tag "Default" nil) (string :tag "Email"))) @@ -148,6 +148,12 @@ The default is to fall back to `add-log-mailing-address' and then `user-mail-add (append (git-get-env-strings env) (list "git") args)) (apply #'call-process "git" nil buffer nil args))) +(defun git-call-process-env-string (env &rest args) + "Wrapper for call-process that sets environment strings, and returns the process output as a string." + (with-temp-buffer + (and (eq 0 (apply #' git-call-process-env t env args)) + (buffer-string)))) + (defun git-run-process-region (buffer start end program args) "Run a git process with a buffer region as input." (let ((output-buffer (current-buffer)) @@ -189,13 +195,15 @@ The default is to fall back to `add-log-mailing-address' and then `user-mail-add (defun git-get-string-sha1 (string) "Read a SHA1 from the specified string." - (let ((pos (string-match "[0-9a-f]\\{40\\}" string))) - (and pos (substring string pos (match-end 0))))) + (and string + (string-match "[0-9a-f]\\{40\\}" string) + (match-string 0 string))) (defun git-get-committer-name () "Return the name to use as GIT_COMMITTER_NAME." ; copied from log-edit (or git-committer-name + (git-repo-config "user.name") (and (boundp 'add-log-full-name) add-log-full-name) (and (fboundp 'user-full-name) (user-full-name)) (and (boundp 'user-full-name) user-full-name))) @@ -204,6 +212,7 @@ The default is to fall back to `add-log-mailing-address' and then `user-mail-add "Return the email address to use as GIT_COMMITTER_EMAIL." ; copied from log-edit (or git-committer-email + (git-repo-config "user.email") (and (boundp 'add-log-mailing-address) add-log-mailing-address) (and (fboundp 'user-mail-address) (user-mail-address)) (and (boundp 'user-mail-address) user-mail-address))) @@ -259,18 +268,17 @@ The default is to fall back to `add-log-mailing-address' and then `user-mail-add (defun git-rev-parse (rev) "Parse a revision name and return its SHA1." (git-get-string-sha1 - (with-output-to-string - (with-current-buffer standard-output - (git-call-process-env t nil "rev-parse" rev))))) + (git-call-process-env-string nil "rev-parse" rev))) + +(defun git-repo-config (key) + "Retrieve the value associated to KEY in the git repository config file." + (let ((str (git-call-process-env-string nil "repo-config" key))) + (and str (car (split-string str "\n"))))) (defun git-symbolic-ref (ref) "Wrapper for the git-symbolic-ref command." - (car - (split-string - (with-output-to-string - (with-current-buffer standard-output - (git-call-process-env t nil "symbolic-ref" ref))) - "\n"))) + (let ((str (git-call-process-env-string nil "symbolic-ref" ref))) + (and str (car (split-string str "\n"))))) (defun git-update-ref (ref val &optional oldval) "Update a reference by calling git-update-ref." @@ -285,11 +293,7 @@ The default is to fall back to `add-log-mailing-address' and then `user-mail-add (defun git-write-tree (&optional index-file) "Call git-write-tree and return the resulting tree SHA1 as a string." (git-get-string-sha1 - (with-output-to-string - (with-current-buffer standard-output - (git-call-process-env t - (if index-file `(("GIT_INDEX_FILE" . ,index-file)) nil) - "write-tree"))))) + (git-call-process-env-string (and index-file `(("GIT_INDEX_FILE" . ,index-file))) "write-tree"))) (defun git-commit-tree (buffer tree head) "Call git-commit-tree with buffer as input and return the resulting commit SHA1." @@ -763,6 +767,16 @@ The default is to fall back to `add-log-mailing-address' and then `user-mail-add (git-setup-diff-buffer (apply #'git-run-command-buffer "*git-diff*" "diff-index" "-p" "-M" "HEAD" "--" (git-get-filenames files))))) +(defun git-diff-file-merge-head (arg) + "Diff the marked file(s) against the first merge head (or the nth one with a numeric prefix)." + (interactive "p") + (let ((files (git-marked-files)) + (merge-heads (git-get-merge-heads))) + (unless merge-heads (error "No merge in progress")) + (git-setup-diff-buffer + (apply #'git-run-command-buffer "*git-diff*" "diff-index" "-p" "-M" + (or (nth (1- arg) merge-heads) "HEAD") "--" (git-get-filenames files))))) + (defun git-diff-unmerged-file (stage) "Diff the marked unmerged file(s) against the specified stage." (let ((files (git-marked-files))) @@ -955,6 +969,7 @@ The default is to fall back to `add-log-mailing-address' and then `user-mail-add (define-key diff-map "=" 'git-diff-file) (define-key diff-map "e" 'git-diff-file-idiff) (define-key diff-map "E" 'git-find-file-imerge) + (define-key diff-map "h" 'git-diff-file-merge-head) (define-key diff-map "m" 'git-diff-file-mine) (define-key diff-map "o" 'git-diff-file-other) (setq git-status-mode-map map))) diff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl index cf233ef6ed..f3fc3ec1a9 100755 --- a/contrib/git-svn/git-svn.perl +++ b/contrib/git-svn/git-svn.perl @@ -850,11 +850,23 @@ sub assert_revision_unknown { } } +sub trees_eq { + my ($x, $y) = @_; + my @x = safe_qx('git-cat-file','commit',$x); + my @y = safe_qx('git-cat-file','commit',$y); + if (($y[0] ne $x[0]) || $x[0] !~ /^tree $sha1\n$/ + || $y[0] !~ /^tree $sha1\n$/) { + print STDERR "Trees not equal: $y[0] != $x[0]\n"; + return 0 + } + return 1; +} + sub assert_revision_eq_or_unknown { my ($revno, $commit) = @_; if (-f "$REV_DIR/$revno") { my $current = file_to_s("$REV_DIR/$revno"); - if ($commit ne $current) { + if (($commit ne $current) && !trees_eq($commit, $current)) { croak "$REV_DIR/$revno already exists!\n", "current: $current\nexpected: $commit\n"; } diff --git a/diff-delta.c b/diff-delta.c index aaee7be4d2..1188b31cd0 100644 --- a/diff-delta.c +++ b/diff-delta.c @@ -136,7 +136,8 @@ void *diff_delta(void *from_buf, unsigned long from_size, unsigned long *delta_size, unsigned long max_size) { - unsigned int i, outpos, outsize, inscnt, hash_shift; + unsigned int i, outpos, outsize, hash_shift; + int inscnt; const unsigned char *ref_data, *ref_top, *data, *top; unsigned char *out; struct index *entry, **hash; @@ -222,6 +223,20 @@ void *diff_delta(void *from_buf, unsigned long from_size, unsigned char *op; if (inscnt) { + while (moff && ref_data[moff-1] == data[-1]) { + if (msize == 0x10000) + break; + /* we can match one byte back */ + msize++; + moff--; + data--; + outpos--; + if (--inscnt) + continue; + outpos--; /* remove count slot */ + inscnt--; /* make it -1 */ + break; + } out[outpos - inscnt - 1] = inscnt; inscnt = 0; } diff --git a/generate-cmdlist.sh b/generate-cmdlist.sh index 6ee85d5a53..6c59dbd68f 100755 --- a/generate-cmdlist.sh +++ b/generate-cmdlist.sh @@ -41,8 +41,12 @@ whatchanged EOF while read cmd do - sed -n "/NAME/,/git-$cmd/H; - \$ {x; s/.*git-$cmd - \\(.*\\)/ {\"$cmd\", \"\1\"},/; p}" \ - "Documentation/git-$cmd.txt" + sed -n ' + /NAME/,/git-'"$cmd"'/H + ${ + x + s/.*git-'"$cmd"' - \(.*\)/ {"'"$cmd"'", "\1"},/ + p + }' "Documentation/git-$cmd.txt" done echo "};" diff --git a/git-commit.sh b/git-commit.sh index 330a434b18..1e7c09e1f2 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -161,7 +161,7 @@ run_status () { } ' - if test -n "$verbose" + if test -n "$verbose" -a -z "$IS_INITIAL" then git-diff-index --cached -M -p --diff-filter=MDTCRA $REFERENCE fi diff --git a/git-fetch.sh b/git-fetch.sh index 0346d4a45c..68356343a6 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -179,6 +179,7 @@ fast_forward_local () { ;; *) echo >&2 " not updating." + exit 1 ;; esac } diff --git a/git-pull.sh b/git-pull.sh index 17fda26721..4611ae644e 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -55,9 +55,17 @@ then # First update the working tree to match $curr_head. echo >&2 "Warning: fetch updated the current branch head." - echo >&2 "Warning: fast forwarding your working tree." + echo >&2 "Warning: fast forwarding your working tree from" + echo >&2 "Warning: $orig_head commit." + git-update-index --refresh 2>/dev/null git-read-tree -u -m "$orig_head" "$curr_head" || - die "You need to first update your working tree." + die 'Cannot fast-forward your working tree. +After making sure that you saved anything precious from +$ git diff '$orig_head' +output, run +$ git reset --hard +to recover.' + fi merge_head=$(sed -e '/ not-for-merge /d' \ @@ -70,14 +78,14 @@ case "$merge_head" in exit 0 ;; ?*' '?*) - var=`git repo-config --get pull.octopus` + var=`git-repo-config --get pull.octopus` if test -n "$var" then strategy_default_args="-s $var" fi ;; *) - var=`git repo-config --get pull.twohead` + var=`git-repo-config --get pull.twohead` if test -n "$var" then strategy_default_args="-s $var" diff --git a/git-send-email.perl b/git-send-email.perl index 7c8d51223f..b220d11cc1 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -307,6 +307,7 @@ $subject = $initial_subject; foreach my $t (@files) { open(F,"<",$t) or die "can't open file $t"; + my $author_not_sender = undef; @cc = @initial_cc; my $found_mbox = 0; my $header_done = 0; @@ -321,7 +322,12 @@ foreach my $t (@files) { $subject = $1; } elsif (/^(Cc|From):\s+(.*)$/) { - next if ($2 eq $from && $suppress_from); + if ($2 eq $from) { + next if ($suppress_from); + } + else { + $author_not_sender = $2; + } printf("(mbox) Adding cc: %s from line '%s'\n", $2, $_) unless $quiet; push @cc, $2; @@ -360,6 +366,9 @@ foreach my $t (@files) { } } close F; + if (defined $author_not_sender) { + $message = "From: $author_not_sender\n\n$message"; + } $cc = join(", ", unique_email_list(@cc)); diff --git a/http-fetch.c b/http-fetch.c index 7de818b109..dc67218ae7 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -8,6 +8,7 @@ #define RANGE_HEADER_SIZE 30 static int got_alternates = -1; +static int corrupt_object_found = 0; static struct curl_slist *no_pragma_header; @@ -834,6 +835,7 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1) obj_req->errorstr, obj_req->curl_result, obj_req->http_code, hex); } else if (obj_req->zret != Z_STREAM_END) { + corrupt_object_found++; ret = error("File %s (%s) corrupt", hex, obj_req->url); } else if (memcmp(obj_req->sha1, obj_req->real_sha1, 20)) { ret = error("File %s has bad hash", hex); @@ -993,5 +995,11 @@ int main(int argc, char **argv) http_cleanup(); + if (corrupt_object_found) { + fprintf(stderr, +"Some loose object were found to be corrupt, but they might be just\n" +"a false '404 Not Found' error message sent with incorrect HTTP\n" +"status code. Suggest running git fsck-objects.\n"); + } return rc; } diff --git a/http-push.c b/http-push.c index 42b0d59e8c..21c5289cde 100644 --- a/http-push.c +++ b/http-push.c @@ -7,6 +7,7 @@ #include "http.h" #include "refs.h" #include "revision.h" +#include "exec_cmd.h" #include <expat.h> @@ -32,6 +33,7 @@ enum XML_Status { #define DAV_PROPFIND "PROPFIND" #define DAV_PUT "PUT" #define DAV_UNLOCK "UNLOCK" +#define DAV_DELETE "DELETE" /* DAV lock flags */ #define DAV_PROP_LOCKWR (1u << 0) @@ -64,9 +66,12 @@ enum XML_Status { #define FETCHING (1u << 7) #define PUSHING (1u << 8) +/* We allow "recursive" symbolic refs. Only within reason, though */ +#define MAXDEPTH 5 + static int pushing = 0; static int aborted = 0; -static char remote_dir_exists[256]; +static signed char remote_dir_exists[256]; static struct curl_slist *no_pragma_header; static struct curl_slist *default_headers; @@ -1858,6 +1863,7 @@ static void one_remote_ref(char *refname) struct ref *ref; unsigned char remote_sha1[20]; struct object *obj; + int len = strlen(refname) + 1; if (fetch_ref(refname, remote_sha1) != 0) { fprintf(stderr, @@ -1879,7 +1885,6 @@ static void one_remote_ref(char *refname) } } - int len = strlen(refname) + 1; ref = xcalloc(1, sizeof(*ref) + len); memcpy(ref->old_sha1, remote_sha1, 20); memcpy(ref->name, refname, len); @@ -2103,6 +2108,197 @@ static int remote_exists(const char *path) return -1; } +static void fetch_symref(char *path, char **symref, unsigned char *sha1) +{ + char *url; + struct buffer buffer; + struct active_request_slot *slot; + struct slot_results results; + + url = xmalloc(strlen(remote->url) + strlen(path) + 1); + sprintf(url, "%s%s", remote->url, path); + + buffer.size = 4096; + buffer.posn = 0; + buffer.buffer = xmalloc(buffer.size); + + slot = get_active_slot(); + slot->results = &results; + curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer); + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); + curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL); + curl_easy_setopt(slot->curl, CURLOPT_URL, url); + if (start_active_slot(slot)) { + run_active_slot(slot); + if (results.curl_result != CURLE_OK) { + die("Couldn't get %s for remote symref\n%s", + url, curl_errorstr); + } + } else { + die("Unable to start remote symref request"); + } + free(url); + + if (*symref != NULL) + free(*symref); + *symref = NULL; + memset(sha1, 0, 20); + + if (buffer.posn == 0) + return; + + /* If it's a symref, set the refname; otherwise try for a sha1 */ + if (!strncmp((char *)buffer.buffer, "ref: ", 5)) { + *symref = xcalloc(buffer.posn - 5, 1); + strncpy(*symref, (char *)buffer.buffer + 5, buffer.posn - 6); + } else { + get_sha1_hex(buffer.buffer, sha1); + } + + free(buffer.buffer); +} + +static int verify_merge_base(unsigned char *head_sha1, unsigned char *branch_sha1) +{ + int pipe_fd[2]; + pid_t merge_base_pid; + char line[PATH_MAX + 20]; + unsigned char merge_sha1[20]; + int verified = 0; + + if (pipe(pipe_fd) < 0) + die("Verify merge base: pipe failed"); + + merge_base_pid = fork(); + if (!merge_base_pid) { + static const char *args[] = { + "merge-base", + "-a", + NULL, + NULL, + NULL + }; + args[2] = strdup(sha1_to_hex(head_sha1)); + args[3] = sha1_to_hex(branch_sha1); + + dup2(pipe_fd[1], 1); + close(pipe_fd[0]); + close(pipe_fd[1]); + execv_git_cmd(args); + die("merge-base setup failed"); + } + if (merge_base_pid < 0) + die("merge-base fork failed"); + + dup2(pipe_fd[0], 0); + close(pipe_fd[0]); + close(pipe_fd[1]); + while (fgets(line, sizeof(line), stdin) != NULL) { + if (get_sha1_hex(line, merge_sha1)) + die("expected sha1, got garbage:\n %s", line); + if (!memcmp(branch_sha1, merge_sha1, 20)) { + verified = 1; + break; + } + } + + return verified; +} + +static int delete_remote_branch(char *pattern, int force) +{ + struct ref *refs = remote_refs; + struct ref *remote_ref = NULL; + unsigned char head_sha1[20]; + char *symref = NULL; + int match; + int patlen = strlen(pattern); + int i; + struct active_request_slot *slot; + struct slot_results results; + char *url; + + /* Find the remote branch(es) matching the specified branch name */ + for (match = 0; refs; refs = refs->next) { + char *name = refs->name; + int namelen = strlen(name); + if (namelen < patlen || + memcmp(name + namelen - patlen, pattern, patlen)) + continue; + if (namelen != patlen && name[namelen - patlen - 1] != '/') + continue; + match++; + remote_ref = refs; + } + if (match == 0) + return error("No remote branch matches %s", pattern); + if (match != 1) + return error("More than one remote branch matches %s", + pattern); + + /* + * Remote HEAD must be a symref (not exactly foolproof; a remote + * symlink to a symref will look like a symref) + */ + fetch_symref("HEAD", &symref, head_sha1); + if (!symref) + return error("Remote HEAD is not a symref"); + + /* Remote branch must not be the remote HEAD */ + for (i=0; symref && i<MAXDEPTH; i++) { + if (!strcmp(remote_ref->name, symref)) + return error("Remote branch %s is the current HEAD", + remote_ref->name); + fetch_symref(symref, &symref, head_sha1); + } + + /* Run extra sanity checks if delete is not forced */ + if (!force) { + /* Remote HEAD must resolve to a known object */ + if (symref) + return error("Remote HEAD symrefs too deep"); + if (is_zero_sha1(head_sha1)) + return error("Unable to resolve remote HEAD"); + if (!has_sha1_file(head_sha1)) + return error("Remote HEAD resolves to object %s\nwhich does not exist locally, perhaps you need to fetch?", sha1_to_hex(head_sha1)); + + /* Remote branch must resolve to a known object */ + if (is_zero_sha1(remote_ref->old_sha1)) + return error("Unable to resolve remote branch %s", + remote_ref->name); + if (!has_sha1_file(remote_ref->old_sha1)) + return error("Remote branch %s resolves to object %s\nwhich does not exist locally, perhaps you need to fetch?", remote_ref->name, sha1_to_hex(remote_ref->old_sha1)); + + /* Remote branch must be an ancestor of remote HEAD */ + if (!verify_merge_base(head_sha1, remote_ref->old_sha1)) { + return error("The branch '%s' is not a strict subset of your current HEAD.\nIf you are sure you want to delete it, run:\n\t'git http-push -D %s %s'", remote_ref->name, remote->url, pattern); + } + } + + /* Send delete request */ + fprintf(stderr, "Removing remote branch '%s'\n", remote_ref->name); + url = xmalloc(strlen(remote->url) + strlen(remote_ref->name) + 1); + sprintf(url, "%s%s", remote->url, remote_ref->name); + slot = get_active_slot(); + slot->results = &results; + curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); + curl_easy_setopt(slot->curl, CURLOPT_URL, url); + curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_DELETE); + if (start_active_slot(slot)) { + run_active_slot(slot); + free(url); + if (results.curl_result != CURLE_OK) + return error("DELETE request failed (%d/%ld)\n", + results.curl_result, results.http_code); + } else { + free(url); + return error("Unable to start DELETE request"); + } + + return 0; +} + int main(int argc, char **argv) { struct transfer_request *request; @@ -2112,9 +2308,13 @@ int main(int argc, char **argv) struct remote_lock *ref_lock = NULL; struct remote_lock *info_ref_lock = NULL; struct rev_info revs; + int delete_branch = 0; + int force_delete = 0; int objects_to_send; int rc = 0; int i; + int new_refs; + struct ref *ref; setup_git_directory(); setup_ident(); @@ -2138,11 +2338,19 @@ int main(int argc, char **argv) push_verbosely = 1; continue; } - usage(http_push_usage); + if (!strcmp(arg, "-d")) { + delete_branch = 1; + continue; + } + if (!strcmp(arg, "-D")) { + delete_branch = 1; + force_delete = 1; + continue; + } } if (!remote->url) { - remote->url = arg; char *path = strstr(arg, "//"); + remote->url = arg; if (path) { path = index(path+2, '/'); if (path) @@ -2158,6 +2366,9 @@ int main(int argc, char **argv) if (!remote->url) usage(http_push_usage); + if (delete_branch && nr_refspec != 1) + die("You must specify only one branch name when deleting a remote branch"); + memset(remote_dir_exists, -1, 256); http_init(); @@ -2193,6 +2404,14 @@ int main(int argc, char **argv) fprintf(stderr, "Fetching remote heads...\n"); get_dav_remote_heads(); + /* Remove a remote branch if -d or -D was specified */ + if (delete_branch) { + if (delete_remote_branch(refspec[0], force_delete) == -1) + fprintf(stderr, "Unable to delete remote branch %s\n", + refspec[0]); + goto cleanup; + } + /* match them up */ if (!remote_tail) remote_tail = &remote_refs; @@ -2204,10 +2423,13 @@ int main(int argc, char **argv) return 0; } - int new_refs = 0; - struct ref *ref; + new_refs = 0; for (ref = remote_refs; ref; ref = ref->next) { char old_hex[60], *new_hex; + const char *commit_argv[4]; + int commit_argc; + char *new_sha1_hex, *old_sha1_hex; + if (!ref->peer_ref) continue; if (!memcmp(ref->old_sha1, ref->peer_ref->new_sha1, 20)) { @@ -2265,10 +2487,9 @@ int main(int argc, char **argv) } /* Set up revision info for this refspec */ - const char *commit_argv[4]; - int commit_argc = 3; - char *new_sha1_hex = strdup(sha1_to_hex(ref->new_sha1)); - char *old_sha1_hex = NULL; + commit_argc = 3; + new_sha1_hex = strdup(sha1_to_hex(ref->new_sha1)); + old_sha1_hex = NULL; commit_argv[1] = "--objects"; commit_argv[2] = new_sha1_hex; if (!push_all && !is_zero_sha1(ref->old_sha1)) { diff --git a/ls-files.c b/ls-files.c index df25c8c012..e42119c5ee 100644 --- a/ls-files.c +++ b/ls-files.c @@ -92,11 +92,12 @@ static int add_excludes_from_file_1(const char *fname, close(fd); return 0; } - buf = xmalloc(size); + buf = xmalloc(size+1); if (read(fd, buf, size) != size) goto err; close(fd); + buf[size++] = '\n'; entry = buf; for (i = 0; i < size; i++) { if (buf[i] == '\n') { diff --git a/revision.c b/revision.c index 01386ed6d4..12cd0529a5 100644 --- a/revision.c +++ b/revision.c @@ -313,8 +313,16 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit) case REV_TREE_NEW: if (revs->remove_empty_trees && rev_same_tree_as_empty(p->tree)) { - *pp = parent->next; - continue; + /* We are adding all the specified + * paths from this parent, so the + * history beyond this parent is not + * interesting. Remove its parents + * (they are grandparents for us). + * IOW, we pretend this parent is a + * "root" commit. + */ + parse_commit(p); + p->parents = NULL; } /* fallthrough */ case REV_TREE_DIFFERENT: diff --git a/sha1_file.c b/sha1_file.c index a80d849f15..58edec0bb6 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -973,6 +973,16 @@ static void *unpack_delta_entry(unsigned char *base_sha1, if (left < 20) die("truncated pack file"); + + /* The base entry _must_ be in the same pack */ + if (!find_pack_entry_one(base_sha1, &base_ent, p)) + die("failed to find delta-pack base object %s", + sha1_to_hex(base_sha1)); + base = unpack_entry_gently(&base_ent, type, &base_size); + if (!base) + die("failed to read delta-pack base object %s", + sha1_to_hex(base_sha1)); + data = base_sha1 + 20; data_size = left - 20; delta_data = xmalloc(delta_size); @@ -990,14 +1000,6 @@ static void *unpack_delta_entry(unsigned char *base_sha1, if ((st != Z_STREAM_END) || stream.total_out != delta_size) die("delta data unpack failed"); - /* The base entry _must_ be in the same pack */ - if (!find_pack_entry_one(base_sha1, &base_ent, p)) - die("failed to find delta-pack base object %s", - sha1_to_hex(base_sha1)); - base = unpack_entry_gently(&base_ent, type, &base_size); - if (!base) - die("failed to read delta-pack base object %s", - sha1_to_hex(base_sha1)); result = patch_delta(base, base_size, delta_data, delta_size, &result_size); |