From c774b2dcf6c16a408757e9da1bf7c006528fc6a6 Mon Sep 17 00:00:00 2001 From: Art Haas Date: Tue, 19 Sep 2006 07:20:19 -0500 Subject: Patch for http-fetch.c and older curl releases Older curl releases do not define CURLE_HTTP_RETURNED_ERROR, they use CURLE_HTTP_NOT_FOUND instead. Newer curl releases keep the CURLE_HTTP_NOT_FOUND definition but using a -DCURL_NO_OLDIES preprocessor flag the old name will not be present in the 'curl.h' header. This patch makes our code written for newer releases of the curl library but allow compiling against an older curl (older than 0x070a03) by defining the missing CURLE_HTTP_RETURNED_ERROR as a synonym for CURLE_HTTP_NOT_FOUND. Signed-off-by: Art Haas Signed-off-by: Junio C Hamano diff --git a/http.h b/http.h index 9ca16ac..6e12e41 100644 --- a/http.h +++ b/http.h @@ -22,6 +22,10 @@ #define NO_CURL_EASY_DUPHANDLE #endif +#if LIBCURL_VERSION_NUM < 0x070a03 +#define CURLE_HTTP_RETURNED_ERROR CURLE_HTTP_NOT_FOUND +#endif + struct slot_results { CURLcode curl_result; -- cgit v0.10.2-6-g49f6 From 8059319acc3638c8398d1bd34f647a2b28f48d5c Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 19 Sep 2006 13:57:03 +0200 Subject: gitweb: Fix mimetype_guess_file for files with multiple extensions Fix getting correct mimetype for "blob_plain" view for files which have multiple extensions, e.g. foo.1.html; now only the last extension is used to find mimetype. Noticed by Martin Waitz. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index c77270c..969c2de 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1197,7 +1197,7 @@ sub mimetype_guess_file { } close(MIME); - $filename =~ /\.(.*?)$/; + $filename =~ /\.([^.]*)$/; return $mimemap{$1}; } -- cgit v0.10.2-6-g49f6 From 45ad9b5096b5b823f8cec562500dc8830d5961b5 Mon Sep 17 00:00:00 2001 From: Jonas Fonseca Date: Wed, 20 Sep 2006 12:15:39 +0200 Subject: Fix trivial typos and inconsistencies in hooks documentation Pointed out by Alan Chandler. Signed-off-by: Jonas Fonseca Signed-off-by: Junio C Hamano diff --git a/Documentation/hooks.txt b/Documentation/hooks.txt index 898b4aa..517f49b 100644 --- a/Documentation/hooks.txt +++ b/Documentation/hooks.txt @@ -5,8 +5,7 @@ Hooks are little scripts you can place in `$GIT_DIR/hooks` directory to trigger action at certain points. When `git-init-db` is run, a handful example hooks are copied in the `hooks` directory of the new repository, but by default they are -all disabled. To enable a hook, make it executable with `chmod -+x`. +all disabled. To enable a hook, make it executable with `chmod +x`. This document describes the currently defined hooks. @@ -16,16 +15,16 @@ applypatch-msg This hook is invoked by `git-applypatch` script, which is typically invoked by `git-applymbox`. It takes a single parameter, the name of the file that holds the proposed commit -log message. Exiting with non-zero status causes the -'git-applypatch' to abort before applying the patch. +log message. Exiting with non-zero status causes +`git-applypatch` to abort before applying the patch. The hook is allowed to edit the message file in place, and can be used to normalize the message into some project standard format (if the project has one). It can also be used to refuse the commit after inspecting the message file. -The default applypatch-msg hook, when enabled, runs the -commit-msg hook, if the latter is enabled. +The default 'applypatch-msg' hook, when enabled, runs the +'commit-msg' hook, if the latter is enabled. pre-applypatch -------------- @@ -39,8 +38,8 @@ after application of the patch not committed. It can be used to inspect the current working tree and refuse to make a commit if it does not pass certain test. -The default pre-applypatch hook, when enabled, runs the -pre-commit hook, if the latter is enabled. +The default 'pre-applypatch' hook, when enabled, runs the +'pre-commit' hook, if the latter is enabled. post-applypatch --------------- @@ -61,9 +60,9 @@ invoked before obtaining the proposed commit log message and making a commit. Exiting with non-zero status from this script causes the `git-commit` to abort. -The default pre-commit hook, when enabled, catches introduction +The default 'pre-commit' hook, when enabled, catches introduction of lines with trailing whitespaces and aborts the commit when -a such line is found. +such a line is found. commit-msg ---------- @@ -79,8 +78,8 @@ be used to normalize the message into some project standard format (if the project has one). It can also be used to refuse the commit after inspecting the message file. -The default commit-msg hook, when enabled, detects duplicate -Signed-off-by: lines, and aborts the commit when one is found. +The default 'commit-msg' hook, when enabled, detects duplicate +"Signed-off-by" lines, and aborts the commit if one is found. post-commit ----------- @@ -91,23 +90,24 @@ parameter, and is invoked after a commit is made. This hook is meant primarily for notification, and cannot affect the outcome of `git-commit`. -The default post-commit hook, when enabled, demonstrates how to +The default 'post-commit' hook, when enabled, demonstrates how to send out a commit notification e-mail. update ------ This hook is invoked by `git-receive-pack` on the remote repository, -which is happens when a `git push` is done on a local repository. +which happens when a `git push` is done on a local repository. Just before updating the ref on the remote repository, the update hook is invoked. Its exit status determines the success or failure of the ref update. The hook executes once for each ref to be updated, and takes three parameters: - - the name of the ref being updated, - - the old object name stored in the ref, - - and the new objectname to be stored in the ref. + + - the name of the ref being updated, + - the old object name stored in the ref, + - and the new objectname to be stored in the ref. A zero exit from the update hook allows the ref to be updated. Exiting with a non-zero status prevents `git-receive-pack` @@ -126,16 +126,16 @@ Another use suggested on the mailing list is to use this hook to implement access control which is finer grained than the one based on filesystem group. -The standard output of this hook is sent to /dev/null; if you -want to report something to the git-send-pack on the other end, -you can redirect your output to your stderr. +The standard output of this hook is sent to `/dev/null`; if you +want to report something to the `git-send-pack` on the other end, +you can redirect your output to your `stderr`. post-update ----------- This hook is invoked by `git-receive-pack` on the remote repository, -which is happens when a `git push` is done on a local repository. +which happens when a `git push` is done on a local repository. It executes on the remote repository once after all the refs have been updated. @@ -145,16 +145,16 @@ name of ref that was actually updated. This hook is meant primarily for notification, and cannot affect the outcome of `git-receive-pack`. -The post-update hook can tell what are the heads that were pushed, +The 'post-update' hook can tell what are the heads that were pushed, but it does not know what their original and updated values are, so it is a poor place to do log old..new. -The default post-update hook, when enabled, runs +When enabled, the default 'post-update' hook runs `git-update-server-info` to keep the information used by dumb -transports (e.g., http) up-to-date. If you are publishing -a git repository that is accessible via http, you should +transports (e.g., HTTP) up-to-date. If you are publishing +a git repository that is accessible via HTTP, you should probably enable this hook. -The standard output of this hook is sent to /dev/null; if you -want to report something to the git-send-pack on the other end, -you can redirect your output to your stderr. +The standard output of this hook is sent to `/dev/null`; if you +want to report something to the `git-send-pack` on the other end, +you can redirect your output to your `stderr`. -- cgit v0.10.2-6-g49f6 From 44d2775a98c14daa77baa66b039080acad5efa0c Mon Sep 17 00:00:00 2001 From: Jonas Fonseca Date: Wed, 20 Sep 2006 12:23:41 +0200 Subject: gitk(1): mention --all Signed-off-by: Jonas Fonseca Signed-off-by: Junio C Hamano diff --git a/Documentation/gitk.txt b/Documentation/gitk.txt index 23be005..f1aeb07 100644 --- a/Documentation/gitk.txt +++ b/Documentation/gitk.txt @@ -37,6 +37,10 @@ frequently used options. Show commits older than a specific date. +--all:: + + Show all branches. + :: Limit the revisions to show. This can be either a single revision @@ -63,6 +67,11 @@ gitk --since="2 weeks ago" \-- gitk:: The "--" is necessary to avoid confusion with the *branch* named 'gitk' +gitk --max-count=100 --all -- Makefile:: + + Show at most 100 changes made to the file 'Makefile'. Instead of only + looking for changes in the current branch look in all branches. + See Also -------- 'qgit(1)':: -- cgit v0.10.2-6-g49f6 From cd90e75ff4a9b01a9cf59505d8d10d79fd1071ca Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Wed, 20 Sep 2006 00:49:51 +0200 Subject: gitweb: Even more support for PATH_INFO based URLs Now the following types of path based URLs are supported: * project overview (summary) page of project * project/branch shortlog of branch * project/branch:file file in branch, blob_plain view * project/branch:dir/ directory listing of dir in branch, tree view The following shortcuts works (see explanation below): * project/branch: directory listing of branch, main tree view * project/:file file in HEAD (raw) * project/:dir/ directory listing of dir in HEAD * project/: directory listing of project's HEAD We use ':' as separator between branch (ref) name and file name (pathname) because valid branch (ref) name cannot have ':' inside. This limit applies to branch name only. This allow for hierarchical branches e.g. topic branch 'topic/subtopic', separate remotes tracking branches e.g. 'refs/remotes/origin/HEAD', and discriminate between head (branch) and tag with the same name. Empty branch should be interpreted as HEAD. If pathname (the part after ':') ends with '/', we assume that pathname is name of directory, and we want to show contents of said directory using "tree" view. If pathname is empty, it is equivalent to '/' (top directory). If pathname (the part after ':') does not end with '/', we assume that pathname is name of file, and we show contents of said file using "blob_plain" view. Pathname is stripped of leading '/', so we can use ':/' to separate branch from pathname. The rationale behind support for PATH_INFO based URLs was to support project web pages for small projects: just create an html branch and then use an URL like http://nowhere.com/gitweb.cgi/project.git/html:/index.html The ':/' syntax allow for working links between .html files served in such way, e.g. link inside "index.html" would get http://nowhere.com/gitweb.cgi/project.git/html:/main.html. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 969c2de..5f597f7 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -274,13 +274,16 @@ sub evaluate_path_info { return if defined $project; my $path_info = $ENV{"PATH_INFO"}; return if !$path_info; - $path_info =~ s,(^/|/$),,gs; - $path_info = validate_input($path_info); + $path_info =~ s,^/+,,; return if !$path_info; + # find which part of PATH_INFO is project $project = $path_info; + $project =~ s,/+$,,; while ($project && !-e "$projectroot/$project/HEAD") { $project =~ s,/*[^/]*$,,; } + # validate project + $project = validate_input($project); if (!$project || ($export_ok && !-e "$projectroot/$project/$export_ok") || ($strict_export && !project_in_list($project))) { @@ -289,15 +292,23 @@ sub evaluate_path_info { } # do not change any parameters if an action is given using the query string return if $action; - if ($path_info =~ m,^$project/([^/]+)/(.+)$,) { - # we got "project.git/branch/filename" - $action ||= "blob_plain"; - $hash_base ||= validate_input($1); - $file_name ||= validate_input($2); - } elsif ($path_info =~ m,^$project/([^/]+)$,) { + $path_info =~ s,^$project/*,,; + my ($refname, $pathname) = split(/:/, $path_info, 2); + if (defined $pathname) { + # we got "project.git/branch:filename" or "project.git/branch:dir/" + # we could use git_get_type(branch:pathname), but it needs $git_dir + $pathname =~ s,^/+,,; + if (!$pathname || substr($pathname, -1) eq "/") { + $action ||= "tree"; + } else { + $action ||= "blob_plain"; + } + $hash_base ||= validate_input($refname); + $file_name ||= validate_input($pathname); + } elsif (defined $refname) { # we got "project.git/branch" $action ||= "shortlog"; - $hash ||= validate_input($1); + $hash ||= validate_input($refname); } } evaluate_path_info(); -- cgit v0.10.2-6-g49f6 From d04d3d424b913332f5c400162f0d87faac1ad3ea Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 19 Sep 2006 21:53:22 +0200 Subject: gitweb: Require project for almost all actions Require that project (repository) is given for all actions except project_list, project_index and opml. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 5f597f7..7fd2e19 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -352,6 +352,10 @@ if (defined $project) { if (!defined($actions{$action})) { die_error(undef, "Unknown action"); } +if ($action !~ m/^(opml|project_list|project_index)$/ && + !$project) { + die_error(undef, "Project needed"); +} $actions{$action}->(); exit; -- cgit v0.10.2-6-g49f6 From 9704d75ddc3e38f4945e23f5afffb849fb51b09f Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 19 Sep 2006 14:31:49 +0200 Subject: gitweb: Always use git-peek-remote in git_get_references Instead of trying to read info/refs file, which might not be present (we did fallback to git-ls-remote), always use git-peek-remote in git_get_references. It is preparation for git_get_refs_info to also return references info. We should not use info/refs for git_get_refs_info as the repository is not served for http-fetch clients. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 7fd2e19..532bd00 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -843,16 +843,10 @@ sub git_get_project_owner { sub git_get_references { my $type = shift || ""; my %refs; - my $fd; # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11 # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{} - if (-f "$projectroot/$project/info/refs") { - open $fd, "$projectroot/$project/info/refs" - or return; - } else { - open $fd, "-|", git_cmd(), "ls-remote", "." - or return; - } + open my $fd, "-|", $GIT, "peek-remote", "$projectroot/$project/" + or return; while (my $line = <$fd>) { chomp $line; -- cgit v0.10.2-6-g49f6 From 120ddde2a843e923944abd5d6e61f8625e820e92 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 19 Sep 2006 14:33:22 +0200 Subject: gitweb: Make git_get_refs_list do work of git_get_references Make git_get_refs_list do also work of git_get_references, to avoid calling git-peek-remote twice. Change meaning of git_get_refs_list meaning: it is now type, and not a full path, e.g. we now use git_get_refs_list("heads") instead of former git_get_refs_list("refs/heads"). Modify git_summary to use only one call to git_get_refs_list instead of one call to git_get_references and two to git_get_refs_list. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 532bd00..0d13b33 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1134,7 +1134,8 @@ sub parse_ls_tree_line ($;%) { ## parse to array of hashes functions sub git_get_refs_list { - my $ref_dir = shift; + my $type = shift || ""; + my %refs; my @reflist; my @refs; @@ -1142,14 +1143,21 @@ sub git_get_refs_list { or return; while (my $line = <$fd>) { chomp $line; - if ($line =~ m/^([0-9a-fA-F]{40})\t$ref_dir\/?([^\^]+)$/) { - push @refs, { hash => $1, name => $2 }; - } elsif ($line =~ m/^[0-9a-fA-F]{40}\t$ref_dir\/?(.*)\^\{\}$/ && - $1 eq $refs[-1]{'name'}) { - # most likely a tag is followed by its peeled - # (deref) one, and when that happens we know the - # previous one was of type 'tag'. - $refs[-1]{'type'} = "tag"; + if ($line =~ m/^([0-9a-fA-F]{40})\trefs\/($type\/?([^\^]+))(\^\{\})?$/) { + if (defined $refs{$1}) { + push @{$refs{$1}}, $2; + } else { + $refs{$1} = [ $2 ]; + } + + if (! $4) { # unpeeled, direct reference + push @refs, { hash => $1, name => $3 }; # without type + } elsif ($3 eq $refs[-1]{'name'}) { + # most likely a tag is followed by its peeled + # (deref) one, and when that happens we know the + # previous one was of type 'tag'. + $refs[-1]{'type'} = "tag"; + } } } close $fd; @@ -1165,7 +1173,7 @@ sub git_get_refs_list { } # sort refs by age @reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist; - return \@reflist; + return (\@reflist, \%refs); } ## ---------------------------------------------------------------------- @@ -2129,14 +2137,14 @@ sub git_tags_body { sub git_heads_body { # uses global variable $project - my ($taglist, $head, $from, $to, $extra) = @_; + my ($headlist, $head, $from, $to, $extra) = @_; $from = 0 unless defined $from; - $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to); + $to = $#{$headlist} if (!defined $to || $#{$headlist} < $to); print "\n"; my $alternate = 0; for (my $i = $from; $i <= $to; $i++) { - my $entry = $taglist->[$i]; + my $entry = $headlist->[$i]; my %tag = %$entry; my $curr = $tag{'id'} eq $head; if ($alternate) { @@ -2306,7 +2314,19 @@ sub git_summary { my $owner = git_get_project_owner($project); - my $refs = git_get_references(); + my ($reflist, $refs) = git_get_refs_list(); + + my @taglist; + my @headlist; + foreach my $ref (@$reflist) { + if ($ref->{'name'} =~ s!^heads/!!) { + push @headlist, $ref; + } else { + $ref->{'name'} =~ s!^tags/!!; + push @taglist, $ref; + } + } + git_header_html(); git_print_page_nav('summary','', $head); @@ -2336,17 +2356,15 @@ sub git_summary { git_shortlog_body(\@revlist, 0, 15, $refs, $cgi->a({-href => href(action=>"shortlog")}, "...")); - my $taglist = git_get_refs_list("refs/tags"); - if (defined @$taglist) { + if (@taglist) { git_print_header_div('tags'); - git_tags_body($taglist, 0, 15, + git_tags_body(\@taglist, 0, 15, $cgi->a({-href => href(action=>"tags")}, "...")); } - my $headlist = git_get_refs_list("refs/heads"); - if (defined @$headlist) { + if (@headlist) { git_print_header_div('heads'); - git_heads_body($headlist, $head, 0, 15, + git_heads_body(\@headlist, $head, 0, 15, $cgi->a({-href => href(action=>"heads")}, "...")); } @@ -2557,7 +2575,7 @@ sub git_tags { git_print_page_nav('','', $head,undef,$head); git_print_header_div('summary', $project); - my $taglist = git_get_refs_list("refs/tags"); + my ($taglist) = git_get_refs_list("tags"); if (defined @$taglist) { git_tags_body($taglist); } @@ -2570,9 +2588,9 @@ sub git_heads { git_print_page_nav('','', $head,undef,$head); git_print_header_div('summary', $project); - my $taglist = git_get_refs_list("refs/heads"); - if (defined @$taglist) { - git_heads_body($taglist, $head); + my ($headlist) = git_get_refs_list("heads"); + if (defined @$headlist) { + git_heads_body($headlist, $head); } git_footer_html(); } -- cgit v0.10.2-6-g49f6 From 62e27f273d66afa996cb7aee6cdb25fbedc053f6 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Tue, 19 Sep 2006 20:47:27 +0200 Subject: gitweb: Fix thinko in git_tags and git_heads git_get_refs_list always return reference to list (and reference to hash which we ignore), so $taglist (in git_tags) and $headlist (in git_heads) are always defined, but @$taglist / @$headlist might be empty. Replaced incorrect "if (defined @$taglist)" with "if (@$taglist)" in git_tags and respectively in git_heads. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 0d13b33..baadbe7 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2576,7 +2576,7 @@ sub git_tags { git_print_header_div('summary', $project); my ($taglist) = git_get_refs_list("tags"); - if (defined @$taglist) { + if (@$taglist) { git_tags_body($taglist); } git_footer_html(); @@ -2589,7 +2589,7 @@ sub git_heads { git_print_header_div('summary', $project); my ($headlist) = git_get_refs_list("heads"); - if (defined @$headlist) { + if (@$headlist) { git_heads_body($headlist, $head); } git_footer_html(); -- cgit v0.10.2-6-g49f6 From 83b5d2f5b0c95fe102bc3d1cc2947abbdf5e5c5b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 17 Sep 2006 16:02:52 -0700 Subject: builtin-grep: make pieces of it available as library. This makes three functions and associated option structures from builtin-grep available from other parts of the system. * options to drive built-in grep engine is stored in struct grep_opt; * pattern strings and extended grep expressions are added to struct grep_opt with append_grep_pattern(); * when finished calling append_grep_pattern(), call compile_grep_patterns() to prepare for execution; * call grep_buffer() to find matches in the in-core buffer. This also adds an internal option "status_only" to grep_opt, which suppresses any output from grep_buffer(). Callers of the function as library can use it to check if there is a match without producing any output. Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 8467447..b15aa9d 100644 --- a/Makefile +++ b/Makefile @@ -234,7 +234,7 @@ LIB_FILE=libgit.a XDIFF_LIB=xdiff/lib.a LIB_H = \ - archive.h blob.h cache.h commit.h csum-file.h delta.h \ + archive.h blob.h cache.h commit.h csum-file.h delta.h grep.h \ diff.h object.h pack.h pkt-line.h quote.h refs.h list-objects.h sideband.h \ run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \ tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h @@ -252,7 +252,7 @@ LIB_OBJS = \ server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \ tag.o tree.o usage.o config.o environment.o ctype.o copy.o \ fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \ - write_or_die.o trace.o list-objects.o \ + write_or_die.o trace.o list-objects.o grep.o \ alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \ color.o wt-status.o diff --git a/builtin-grep.c b/builtin-grep.c index ed87a55..6718788 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -11,6 +11,7 @@ #include "tree-walk.h" #include "builtin.h" #include +#include "grep.h" #include #include @@ -82,498 +83,6 @@ static int pathspec_matches(const char **paths, const char *name) return 0; } -enum grep_pat_token { - GREP_PATTERN, - GREP_AND, - GREP_OPEN_PAREN, - GREP_CLOSE_PAREN, - GREP_NOT, - GREP_OR, -}; - -struct grep_pat { - struct grep_pat *next; - const char *origin; - int no; - enum grep_pat_token token; - const char *pattern; - regex_t regexp; -}; - -enum grep_expr_node { - GREP_NODE_ATOM, - GREP_NODE_NOT, - GREP_NODE_AND, - GREP_NODE_OR, -}; - -struct grep_expr { - enum grep_expr_node node; - union { - struct grep_pat *atom; - struct grep_expr *unary; - struct { - struct grep_expr *left; - struct grep_expr *right; - } binary; - } u; -}; - -struct grep_opt { - struct grep_pat *pattern_list; - struct grep_pat **pattern_tail; - struct grep_expr *pattern_expression; - int prefix_length; - regex_t regexp; - unsigned linenum:1; - unsigned invert:1; - unsigned name_only:1; - unsigned unmatch_name_only:1; - unsigned count:1; - unsigned word_regexp:1; - unsigned fixed:1; -#define GREP_BINARY_DEFAULT 0 -#define GREP_BINARY_NOMATCH 1 -#define GREP_BINARY_TEXT 2 - unsigned binary:2; - unsigned extended:1; - unsigned relative:1; - unsigned pathname:1; - int regflags; - unsigned pre_context; - unsigned post_context; -}; - -static void add_pattern(struct grep_opt *opt, const char *pat, - const char *origin, int no, enum grep_pat_token t) -{ - struct grep_pat *p = xcalloc(1, sizeof(*p)); - p->pattern = pat; - p->origin = origin; - p->no = no; - p->token = t; - *opt->pattern_tail = p; - opt->pattern_tail = &p->next; - p->next = NULL; -} - -static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) -{ - int err = regcomp(&p->regexp, p->pattern, opt->regflags); - if (err) { - char errbuf[1024]; - char where[1024]; - if (p->no) - sprintf(where, "In '%s' at %d, ", - p->origin, p->no); - else if (p->origin) - sprintf(where, "%s, ", p->origin); - else - where[0] = 0; - regerror(err, &p->regexp, errbuf, 1024); - regfree(&p->regexp); - die("%s'%s': %s", where, p->pattern, errbuf); - } -} - -static struct grep_expr *compile_pattern_expr(struct grep_pat **); -static struct grep_expr *compile_pattern_atom(struct grep_pat **list) -{ - struct grep_pat *p; - struct grep_expr *x; - - p = *list; - switch (p->token) { - case GREP_PATTERN: /* atom */ - x = xcalloc(1, sizeof (struct grep_expr)); - x->node = GREP_NODE_ATOM; - x->u.atom = p; - *list = p->next; - return x; - case GREP_OPEN_PAREN: - *list = p->next; - x = compile_pattern_expr(list); - if (!x) - return NULL; - if (!*list || (*list)->token != GREP_CLOSE_PAREN) - die("unmatched parenthesis"); - *list = (*list)->next; - return x; - default: - return NULL; - } -} - -static struct grep_expr *compile_pattern_not(struct grep_pat **list) -{ - struct grep_pat *p; - struct grep_expr *x; - - p = *list; - switch (p->token) { - case GREP_NOT: - if (!p->next) - die("--not not followed by pattern expression"); - *list = p->next; - x = xcalloc(1, sizeof (struct grep_expr)); - x->node = GREP_NODE_NOT; - x->u.unary = compile_pattern_not(list); - if (!x->u.unary) - die("--not followed by non pattern expression"); - return x; - default: - return compile_pattern_atom(list); - } -} - -static struct grep_expr *compile_pattern_and(struct grep_pat **list) -{ - struct grep_pat *p; - struct grep_expr *x, *y, *z; - - x = compile_pattern_not(list); - p = *list; - if (p && p->token == GREP_AND) { - if (!p->next) - die("--and not followed by pattern expression"); - *list = p->next; - y = compile_pattern_and(list); - if (!y) - die("--and not followed by pattern expression"); - z = xcalloc(1, sizeof (struct grep_expr)); - z->node = GREP_NODE_AND; - z->u.binary.left = x; - z->u.binary.right = y; - return z; - } - return x; -} - -static struct grep_expr *compile_pattern_or(struct grep_pat **list) -{ - struct grep_pat *p; - struct grep_expr *x, *y, *z; - - x = compile_pattern_and(list); - p = *list; - if (x && p && p->token != GREP_CLOSE_PAREN) { - y = compile_pattern_or(list); - if (!y) - die("not a pattern expression %s", p->pattern); - z = xcalloc(1, sizeof (struct grep_expr)); - z->node = GREP_NODE_OR; - z->u.binary.left = x; - z->u.binary.right = y; - return z; - } - return x; -} - -static struct grep_expr *compile_pattern_expr(struct grep_pat **list) -{ - return compile_pattern_or(list); -} - -static void compile_patterns(struct grep_opt *opt) -{ - struct grep_pat *p; - - /* First compile regexps */ - for (p = opt->pattern_list; p; p = p->next) { - if (p->token == GREP_PATTERN) - compile_regexp(p, opt); - else - opt->extended = 1; - } - - if (!opt->extended) - return; - - /* Then bundle them up in an expression. - * A classic recursive descent parser would do. - */ - p = opt->pattern_list; - opt->pattern_expression = compile_pattern_expr(&p); - if (p) - die("incomplete pattern expression: %s", p->pattern); -} - -static char *end_of_line(char *cp, unsigned long *left) -{ - unsigned long l = *left; - while (l && *cp != '\n') { - l--; - cp++; - } - *left = l; - return cp; -} - -static int word_char(char ch) -{ - return isalnum(ch) || ch == '_'; -} - -static void show_line(struct grep_opt *opt, const char *bol, const char *eol, - const char *name, unsigned lno, char sign) -{ - if (opt->pathname) - printf("%s%c", name, sign); - if (opt->linenum) - printf("%d%c", lno, sign); - printf("%.*s\n", (int)(eol-bol), bol); -} - -/* - * NEEDSWORK: share code with diff.c - */ -#define FIRST_FEW_BYTES 8000 -static int buffer_is_binary(const char *ptr, unsigned long size) -{ - if (FIRST_FEW_BYTES < size) - size = FIRST_FEW_BYTES; - return !!memchr(ptr, 0, size); -} - -static int fixmatch(const char *pattern, char *line, regmatch_t *match) -{ - char *hit = strstr(line, pattern); - if (!hit) { - match->rm_so = match->rm_eo = -1; - return REG_NOMATCH; - } - else { - match->rm_so = hit - line; - match->rm_eo = match->rm_so + strlen(pattern); - return 0; - } -} - -static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol) -{ - int hit = 0; - int at_true_bol = 1; - regmatch_t pmatch[10]; - - again: - if (!opt->fixed) { - regex_t *exp = &p->regexp; - hit = !regexec(exp, bol, ARRAY_SIZE(pmatch), - pmatch, 0); - } - else { - hit = !fixmatch(p->pattern, bol, pmatch); - } - - if (hit && opt->word_regexp) { - if ((pmatch[0].rm_so < 0) || - (eol - bol) <= pmatch[0].rm_so || - (pmatch[0].rm_eo < 0) || - (eol - bol) < pmatch[0].rm_eo) - die("regexp returned nonsense"); - - /* Match beginning must be either beginning of the - * line, or at word boundary (i.e. the last char must - * not be a word char). Similarly, match end must be - * either end of the line, or at word boundary - * (i.e. the next char must not be a word char). - */ - if ( ((pmatch[0].rm_so == 0 && at_true_bol) || - !word_char(bol[pmatch[0].rm_so-1])) && - ((pmatch[0].rm_eo == (eol-bol)) || - !word_char(bol[pmatch[0].rm_eo])) ) - ; - else - hit = 0; - - if (!hit && pmatch[0].rm_so + bol + 1 < eol) { - /* There could be more than one match on the - * line, and the first match might not be - * strict word match. But later ones could be! - */ - bol = pmatch[0].rm_so + bol + 1; - at_true_bol = 0; - goto again; - } - } - return hit; -} - -static int match_expr_eval(struct grep_opt *opt, - struct grep_expr *x, - char *bol, char *eol) -{ - switch (x->node) { - case GREP_NODE_ATOM: - return match_one_pattern(opt, x->u.atom, bol, eol); - break; - case GREP_NODE_NOT: - return !match_expr_eval(opt, x->u.unary, bol, eol); - case GREP_NODE_AND: - return (match_expr_eval(opt, x->u.binary.left, bol, eol) && - match_expr_eval(opt, x->u.binary.right, bol, eol)); - case GREP_NODE_OR: - return (match_expr_eval(opt, x->u.binary.left, bol, eol) || - match_expr_eval(opt, x->u.binary.right, bol, eol)); - } - die("Unexpected node type (internal error) %d\n", x->node); -} - -static int match_expr(struct grep_opt *opt, char *bol, char *eol) -{ - struct grep_expr *x = opt->pattern_expression; - return match_expr_eval(opt, x, bol, eol); -} - -static int match_line(struct grep_opt *opt, char *bol, char *eol) -{ - struct grep_pat *p; - if (opt->extended) - return match_expr(opt, bol, eol); - for (p = opt->pattern_list; p; p = p->next) { - if (match_one_pattern(opt, p, bol, eol)) - return 1; - } - return 0; -} - -static int grep_buffer(struct grep_opt *opt, const char *name, - char *buf, unsigned long size) -{ - char *bol = buf; - unsigned long left = size; - unsigned lno = 1; - struct pre_context_line { - char *bol; - char *eol; - } *prev = NULL, *pcl; - unsigned last_hit = 0; - unsigned last_shown = 0; - int binary_match_only = 0; - const char *hunk_mark = ""; - unsigned count = 0; - - if (buffer_is_binary(buf, size)) { - switch (opt->binary) { - case GREP_BINARY_DEFAULT: - binary_match_only = 1; - break; - case GREP_BINARY_NOMATCH: - return 0; /* Assume unmatch */ - break; - default: - break; - } - } - - if (opt->pre_context) - prev = xcalloc(opt->pre_context, sizeof(*prev)); - if (opt->pre_context || opt->post_context) - hunk_mark = "--\n"; - - while (left) { - char *eol, ch; - int hit = 0; - - eol = end_of_line(bol, &left); - ch = *eol; - *eol = 0; - - hit = match_line(opt, bol, eol); - - /* "grep -v -e foo -e bla" should list lines - * that do not have either, so inversion should - * be done outside. - */ - if (opt->invert) - hit = !hit; - if (opt->unmatch_name_only) { - if (hit) - return 0; - goto next_line; - } - if (hit) { - count++; - if (binary_match_only) { - printf("Binary file %s matches\n", name); - return 1; - } - if (opt->name_only) { - printf("%s\n", name); - return 1; - } - /* Hit at this line. If we haven't shown the - * pre-context lines, we would need to show them. - * When asked to do "count", this still show - * the context which is nonsense, but the user - * deserves to get that ;-). - */ - if (opt->pre_context) { - unsigned from; - if (opt->pre_context < lno) - from = lno - opt->pre_context; - else - from = 1; - if (from <= last_shown) - from = last_shown + 1; - if (last_shown && from != last_shown + 1) - printf(hunk_mark); - while (from < lno) { - pcl = &prev[lno-from-1]; - show_line(opt, pcl->bol, pcl->eol, - name, from, '-'); - from++; - } - last_shown = lno-1; - } - if (last_shown && lno != last_shown + 1) - printf(hunk_mark); - if (!opt->count) - show_line(opt, bol, eol, name, lno, ':'); - last_shown = last_hit = lno; - } - else if (last_hit && - lno <= last_hit + opt->post_context) { - /* If the last hit is within the post context, - * we need to show this line. - */ - if (last_shown && lno != last_shown + 1) - printf(hunk_mark); - show_line(opt, bol, eol, name, lno, '-'); - last_shown = lno; - } - if (opt->pre_context) { - memmove(prev+1, prev, - (opt->pre_context-1) * sizeof(*prev)); - prev->bol = bol; - prev->eol = eol; - } - - next_line: - *eol = ch; - bol = eol + 1; - if (!left) - break; - left--; - lno++; - } - - if (opt->unmatch_name_only) { - /* We did not see any hit, so we want to show this */ - printf("%s\n", name); - return 1; - } - - /* NEEDSWORK: - * The real "grep -c foo *.c" gives many "bar.c:0" lines, - * which feels mostly useless but sometimes useful. Maybe - * make it another option? For now suppress them. - */ - if (opt->count && count) - printf("%s:%u\n", name, count); - return !!last_hit; -} - static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char *name, int tree_name_len) { unsigned long size; @@ -1055,8 +564,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) /* ignore empty line like grep does */ if (!buf[0]) continue; - add_pattern(&opt, xstrdup(buf), argv[1], ++lno, - GREP_PATTERN); + append_grep_pattern(&opt, xstrdup(buf), + argv[1], ++lno, + GREP_PATTERN); } fclose(patterns); argv++; @@ -1064,27 +574,32 @@ int cmd_grep(int argc, const char **argv, const char *prefix) continue; } if (!strcmp("--not", arg)) { - add_pattern(&opt, arg, "command line", 0, GREP_NOT); + append_grep_pattern(&opt, arg, "command line", 0, + GREP_NOT); continue; } if (!strcmp("--and", arg)) { - add_pattern(&opt, arg, "command line", 0, GREP_AND); + append_grep_pattern(&opt, arg, "command line", 0, + GREP_AND); continue; } if (!strcmp("--or", arg)) continue; /* no-op */ if (!strcmp("(", arg)) { - add_pattern(&opt, arg, "command line", 0, GREP_OPEN_PAREN); + append_grep_pattern(&opt, arg, "command line", 0, + GREP_OPEN_PAREN); continue; } if (!strcmp(")", arg)) { - add_pattern(&opt, arg, "command line", 0, GREP_CLOSE_PAREN); + append_grep_pattern(&opt, arg, "command line", 0, + GREP_CLOSE_PAREN); continue; } if (!strcmp("-e", arg)) { if (1 < argc) { - add_pattern(&opt, argv[1], "-e option", 0, - GREP_PATTERN); + append_grep_pattern(&opt, argv[1], + "-e option", 0, + GREP_PATTERN); argv++; argc--; continue; @@ -1106,8 +621,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) /* First unrecognized non-option token */ if (!opt.pattern_list) { - add_pattern(&opt, arg, "command line", 0, - GREP_PATTERN); + append_grep_pattern(&opt, arg, "command line", 0, + GREP_PATTERN); break; } else { @@ -1124,8 +639,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) die("no pattern given."); if ((opt.regflags != REG_NEWLINE) && opt.fixed) die("cannot mix --fixed-strings and regexp"); - if (!opt.fixed) - compile_patterns(&opt); + compile_grep_patterns(&opt); /* Check revs and then paths */ for (i = 1; i < argc; i++) { diff --git a/grep.c b/grep.c new file mode 100644 index 0000000..61db6e1 --- /dev/null +++ b/grep.c @@ -0,0 +1,440 @@ +#include "cache.h" +#include +#include "grep.h" + +void append_grep_pattern(struct grep_opt *opt, const char *pat, + const char *origin, int no, enum grep_pat_token t) +{ + struct grep_pat *p = xcalloc(1, sizeof(*p)); + p->pattern = pat; + p->origin = origin; + p->no = no; + p->token = t; + *opt->pattern_tail = p; + opt->pattern_tail = &p->next; + p->next = NULL; +} + +static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) +{ + int err = regcomp(&p->regexp, p->pattern, opt->regflags); + if (err) { + char errbuf[1024]; + char where[1024]; + if (p->no) + sprintf(where, "In '%s' at %d, ", + p->origin, p->no); + else if (p->origin) + sprintf(where, "%s, ", p->origin); + else + where[0] = 0; + regerror(err, &p->regexp, errbuf, 1024); + regfree(&p->regexp); + die("%s'%s': %s", where, p->pattern, errbuf); + } +} + +static struct grep_expr *compile_pattern_expr(struct grep_pat **); +static struct grep_expr *compile_pattern_atom(struct grep_pat **list) +{ + struct grep_pat *p; + struct grep_expr *x; + + p = *list; + switch (p->token) { + case GREP_PATTERN: /* atom */ + x = xcalloc(1, sizeof (struct grep_expr)); + x->node = GREP_NODE_ATOM; + x->u.atom = p; + *list = p->next; + return x; + case GREP_OPEN_PAREN: + *list = p->next; + x = compile_pattern_expr(list); + if (!x) + return NULL; + if (!*list || (*list)->token != GREP_CLOSE_PAREN) + die("unmatched parenthesis"); + *list = (*list)->next; + return x; + default: + return NULL; + } +} + +static struct grep_expr *compile_pattern_not(struct grep_pat **list) +{ + struct grep_pat *p; + struct grep_expr *x; + + p = *list; + switch (p->token) { + case GREP_NOT: + if (!p->next) + die("--not not followed by pattern expression"); + *list = p->next; + x = xcalloc(1, sizeof (struct grep_expr)); + x->node = GREP_NODE_NOT; + x->u.unary = compile_pattern_not(list); + if (!x->u.unary) + die("--not followed by non pattern expression"); + return x; + default: + return compile_pattern_atom(list); + } +} + +static struct grep_expr *compile_pattern_and(struct grep_pat **list) +{ + struct grep_pat *p; + struct grep_expr *x, *y, *z; + + x = compile_pattern_not(list); + p = *list; + if (p && p->token == GREP_AND) { + if (!p->next) + die("--and not followed by pattern expression"); + *list = p->next; + y = compile_pattern_and(list); + if (!y) + die("--and not followed by pattern expression"); + z = xcalloc(1, sizeof (struct grep_expr)); + z->node = GREP_NODE_AND; + z->u.binary.left = x; + z->u.binary.right = y; + return z; + } + return x; +} + +static struct grep_expr *compile_pattern_or(struct grep_pat **list) +{ + struct grep_pat *p; + struct grep_expr *x, *y, *z; + + x = compile_pattern_and(list); + p = *list; + if (x && p && p->token != GREP_CLOSE_PAREN) { + y = compile_pattern_or(list); + if (!y) + die("not a pattern expression %s", p->pattern); + z = xcalloc(1, sizeof (struct grep_expr)); + z->node = GREP_NODE_OR; + z->u.binary.left = x; + z->u.binary.right = y; + return z; + } + return x; +} + +static struct grep_expr *compile_pattern_expr(struct grep_pat **list) +{ + return compile_pattern_or(list); +} + +void compile_grep_patterns(struct grep_opt *opt) +{ + struct grep_pat *p; + + if (opt->fixed) + return; + + /* First compile regexps */ + for (p = opt->pattern_list; p; p = p->next) { + if (p->token == GREP_PATTERN) + compile_regexp(p, opt); + else + opt->extended = 1; + } + + if (!opt->extended) + return; + + /* Then bundle them up in an expression. + * A classic recursive descent parser would do. + */ + p = opt->pattern_list; + opt->pattern_expression = compile_pattern_expr(&p); + if (p) + die("incomplete pattern expression: %s", p->pattern); +} + +static char *end_of_line(char *cp, unsigned long *left) +{ + unsigned long l = *left; + while (l && *cp != '\n') { + l--; + cp++; + } + *left = l; + return cp; +} + +static int word_char(char ch) +{ + return isalnum(ch) || ch == '_'; +} + +static void show_line(struct grep_opt *opt, const char *bol, const char *eol, + const char *name, unsigned lno, char sign) +{ + if (opt->pathname) + printf("%s%c", name, sign); + if (opt->linenum) + printf("%d%c", lno, sign); + printf("%.*s\n", (int)(eol-bol), bol); +} + +/* + * NEEDSWORK: share code with diff.c + */ +#define FIRST_FEW_BYTES 8000 +static int buffer_is_binary(const char *ptr, unsigned long size) +{ + if (FIRST_FEW_BYTES < size) + size = FIRST_FEW_BYTES; + return !!memchr(ptr, 0, size); +} + +static int fixmatch(const char *pattern, char *line, regmatch_t *match) +{ + char *hit = strstr(line, pattern); + if (!hit) { + match->rm_so = match->rm_eo = -1; + return REG_NOMATCH; + } + else { + match->rm_so = hit - line; + match->rm_eo = match->rm_so + strlen(pattern); + return 0; + } +} + +static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol) +{ + int hit = 0; + int at_true_bol = 1; + regmatch_t pmatch[10]; + + again: + if (!opt->fixed) { + regex_t *exp = &p->regexp; + hit = !regexec(exp, bol, ARRAY_SIZE(pmatch), + pmatch, 0); + } + else { + hit = !fixmatch(p->pattern, bol, pmatch); + } + + if (hit && opt->word_regexp) { + if ((pmatch[0].rm_so < 0) || + (eol - bol) <= pmatch[0].rm_so || + (pmatch[0].rm_eo < 0) || + (eol - bol) < pmatch[0].rm_eo) + die("regexp returned nonsense"); + + /* Match beginning must be either beginning of the + * line, or at word boundary (i.e. the last char must + * not be a word char). Similarly, match end must be + * either end of the line, or at word boundary + * (i.e. the next char must not be a word char). + */ + if ( ((pmatch[0].rm_so == 0 && at_true_bol) || + !word_char(bol[pmatch[0].rm_so-1])) && + ((pmatch[0].rm_eo == (eol-bol)) || + !word_char(bol[pmatch[0].rm_eo])) ) + ; + else + hit = 0; + + if (!hit && pmatch[0].rm_so + bol + 1 < eol) { + /* There could be more than one match on the + * line, and the first match might not be + * strict word match. But later ones could be! + */ + bol = pmatch[0].rm_so + bol + 1; + at_true_bol = 0; + goto again; + } + } + return hit; +} + +static int match_expr_eval(struct grep_opt *opt, + struct grep_expr *x, + char *bol, char *eol) +{ + switch (x->node) { + case GREP_NODE_ATOM: + return match_one_pattern(opt, x->u.atom, bol, eol); + break; + case GREP_NODE_NOT: + return !match_expr_eval(opt, x->u.unary, bol, eol); + case GREP_NODE_AND: + return (match_expr_eval(opt, x->u.binary.left, bol, eol) && + match_expr_eval(opt, x->u.binary.right, bol, eol)); + case GREP_NODE_OR: + return (match_expr_eval(opt, x->u.binary.left, bol, eol) || + match_expr_eval(opt, x->u.binary.right, bol, eol)); + } + die("Unexpected node type (internal error) %d\n", x->node); +} + +static int match_expr(struct grep_opt *opt, char *bol, char *eol) +{ + struct grep_expr *x = opt->pattern_expression; + return match_expr_eval(opt, x, bol, eol); +} + +static int match_line(struct grep_opt *opt, char *bol, char *eol) +{ + struct grep_pat *p; + if (opt->extended) + return match_expr(opt, bol, eol); + for (p = opt->pattern_list; p; p = p->next) { + if (match_one_pattern(opt, p, bol, eol)) + return 1; + } + return 0; +} + +int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size) +{ + char *bol = buf; + unsigned long left = size; + unsigned lno = 1; + struct pre_context_line { + char *bol; + char *eol; + } *prev = NULL, *pcl; + unsigned last_hit = 0; + unsigned last_shown = 0; + int binary_match_only = 0; + const char *hunk_mark = ""; + unsigned count = 0; + + if (buffer_is_binary(buf, size)) { + switch (opt->binary) { + case GREP_BINARY_DEFAULT: + binary_match_only = 1; + break; + case GREP_BINARY_NOMATCH: + return 0; /* Assume unmatch */ + break; + default: + break; + } + } + + if (opt->pre_context) + prev = xcalloc(opt->pre_context, sizeof(*prev)); + if (opt->pre_context || opt->post_context) + hunk_mark = "--\n"; + + while (left) { + char *eol, ch; + int hit = 0; + + eol = end_of_line(bol, &left); + ch = *eol; + *eol = 0; + + hit = match_line(opt, bol, eol); + *eol = ch; + + /* "grep -v -e foo -e bla" should list lines + * that do not have either, so inversion should + * be done outside. + */ + if (opt->invert) + hit = !hit; + if (opt->unmatch_name_only) { + if (hit) + return 0; + goto next_line; + } + if (hit) { + count++; + if (opt->status_only) + return 1; + if (binary_match_only) { + printf("Binary file %s matches\n", name); + return 1; + } + if (opt->name_only) { + printf("%s\n", name); + return 1; + } + /* Hit at this line. If we haven't shown the + * pre-context lines, we would need to show them. + * When asked to do "count", this still show + * the context which is nonsense, but the user + * deserves to get that ;-). + */ + if (opt->pre_context) { + unsigned from; + if (opt->pre_context < lno) + from = lno - opt->pre_context; + else + from = 1; + if (from <= last_shown) + from = last_shown + 1; + if (last_shown && from != last_shown + 1) + printf(hunk_mark); + while (from < lno) { + pcl = &prev[lno-from-1]; + show_line(opt, pcl->bol, pcl->eol, + name, from, '-'); + from++; + } + last_shown = lno-1; + } + if (last_shown && lno != last_shown + 1) + printf(hunk_mark); + if (!opt->count) + show_line(opt, bol, eol, name, lno, ':'); + last_shown = last_hit = lno; + } + else if (last_hit && + lno <= last_hit + opt->post_context) { + /* If the last hit is within the post context, + * we need to show this line. + */ + if (last_shown && lno != last_shown + 1) + printf(hunk_mark); + show_line(opt, bol, eol, name, lno, '-'); + last_shown = lno; + } + if (opt->pre_context) { + memmove(prev+1, prev, + (opt->pre_context-1) * sizeof(*prev)); + prev->bol = bol; + prev->eol = eol; + } + + next_line: + bol = eol + 1; + if (!left) + break; + left--; + lno++; + } + + if (opt->status_only) + return 0; + if (opt->unmatch_name_only) { + /* We did not see any hit, so we want to show this */ + printf("%s\n", name); + return 1; + } + + /* NEEDSWORK: + * The real "grep -c foo *.c" gives many "bar.c:0" lines, + * which feels mostly useless but sometimes useful. Maybe + * make it another option? For now suppress them. + */ + if (opt->count && count) + printf("%s:%u\n", name, count); + return !!last_hit; +} + diff --git a/grep.h b/grep.h new file mode 100644 index 0000000..80122b0 --- /dev/null +++ b/grep.h @@ -0,0 +1,71 @@ +#ifndef GREP_H +#define GREP_H + +enum grep_pat_token { + GREP_PATTERN, + GREP_AND, + GREP_OPEN_PAREN, + GREP_CLOSE_PAREN, + GREP_NOT, + GREP_OR, +}; + +struct grep_pat { + struct grep_pat *next; + const char *origin; + int no; + enum grep_pat_token token; + const char *pattern; + regex_t regexp; +}; + +enum grep_expr_node { + GREP_NODE_ATOM, + GREP_NODE_NOT, + GREP_NODE_AND, + GREP_NODE_OR, +}; + +struct grep_expr { + enum grep_expr_node node; + union { + struct grep_pat *atom; + struct grep_expr *unary; + struct { + struct grep_expr *left; + struct grep_expr *right; + } binary; + } u; +}; + +struct grep_opt { + struct grep_pat *pattern_list; + struct grep_pat **pattern_tail; + struct grep_expr *pattern_expression; + int prefix_length; + regex_t regexp; + unsigned linenum:1; + unsigned invert:1; + unsigned status_only:1; + unsigned name_only:1; + unsigned unmatch_name_only:1; + unsigned count:1; + unsigned word_regexp:1; + unsigned fixed:1; +#define GREP_BINARY_DEFAULT 0 +#define GREP_BINARY_NOMATCH 1 +#define GREP_BINARY_TEXT 2 + unsigned binary:2; + unsigned extended:1; + unsigned relative:1; + unsigned pathname:1; + int regflags; + unsigned pre_context; + unsigned post_context; +}; + +extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t); +extern void compile_grep_patterns(struct grep_opt *opt); +extern int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size); + +#endif -- cgit v0.10.2-6-g49f6 From 8ecae9b032cd0427079d557a3bb6c39116420d4b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 17 Sep 2006 15:43:40 -0700 Subject: revision traversal: prepare for commit log match. This is from a suggestion by Linus, just to mark the locations where we need to modify to actually implement the filtering. We do not have any actual filtering code yet. Signed-off-by: Junio C Hamano diff --git a/revision.c b/revision.c index 6a2539b..a14457a 100644 --- a/revision.c +++ b/revision.c @@ -6,6 +6,8 @@ #include "diff.h" #include "refs.h" #include "revision.h" +#include +#include "grep.h" static char *path_name(struct name_path *path, const char *name) { @@ -1045,6 +1047,15 @@ static void mark_boundary_to_show(struct commit *commit) } } +static int commit_match(struct commit *commit, struct rev_info *opt) +{ + if (!opt->header_filter && !opt->message_filter) + return 1; + + /* match it here */ + return 1; +} + struct commit *get_revision(struct rev_info *revs) { struct commit_list *list = revs->commits; @@ -1105,6 +1116,8 @@ struct commit *get_revision(struct rev_info *revs) if (revs->no_merges && commit->parents && commit->parents->next) continue; + if (!commit_match(commit, revs)) + continue; if (revs->prune_fn && revs->dense) { /* Commit without changes? */ if (!(commit->object.flags & TREECHANGE)) { diff --git a/revision.h b/revision.h index a5c35d0..60030e5 100644 --- a/revision.h +++ b/revision.h @@ -71,6 +71,10 @@ struct rev_info { const char *add_signoff; const char *extra_headers; + /* Filter by commit log message */ + struct grep_opt *header_filter; + struct grep_opt *message_filter; + /* special limits */ int max_count; unsigned long max_age; -- cgit v0.10.2-6-g49f6 From bd95fcd34543d7d98bff033c00054341165bc9ce Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 17 Sep 2006 17:23:20 -0700 Subject: revision traversal: --author, --committer, and --grep. This adds three options to setup_revisions(), which lets you filter resulting commits by the author name, the committer name and the log message with regexp. Signed-off-by: Junio C Hamano diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index 28966ad..00a95e2 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -20,6 +20,7 @@ SYNOPSIS [ \--stdin ] [ \--topo-order ] [ \--parents ] + [ \--(author|committer|grep)= ] [ [\--objects | \--objects-edge] [ \--unpacked ] ] [ \--pretty | \--header ] [ \--bisect ] @@ -154,6 +155,16 @@ limiting may be applied. Limit the commits output to specified time range. +--author='pattern', --committer='pattern':: + + Limit the commits output to ones with author/committer + header lines that match the specified pattern. + +--grep='pattern':: + + Limit the commits output to ones with log message that + matches the specified pattern. + --remove-empty:: Stop when a given path disappears from the tree. diff --git a/revision.c b/revision.c index a14457a..26dd418 100644 --- a/revision.c +++ b/revision.c @@ -674,6 +674,40 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, return 0; } +static void add_header_grep(struct rev_info *revs, const char *field, const char *pattern) +{ + char *pat; + int patlen, fldlen; + + if (!revs->header_filter) { + struct grep_opt *opt = xcalloc(1, sizeof(*opt)); + opt->status_only = 1; + opt->pattern_tail = &(opt->pattern_list); + opt->regflags = REG_NEWLINE; + revs->header_filter = opt; + } + + fldlen = strlen(field); + patlen = strlen(pattern); + pat = xmalloc(patlen + fldlen + 3); + sprintf(pat, "^%s %s", field, pattern); + append_grep_pattern(revs->header_filter, pat, + "command line", 0, GREP_PATTERN); +} + +static void add_message_grep(struct rev_info *revs, const char *pattern) +{ + if (!revs->message_filter) { + struct grep_opt *opt = xcalloc(1, sizeof(*opt)); + opt->status_only = 1; + opt->pattern_tail = &(opt->pattern_list); + opt->regflags = REG_NEWLINE; + revs->message_filter = opt; + } + append_grep_pattern(revs->message_filter, pattern, + "command line", 0, GREP_PATTERN); +} + static void add_ignore_packed(struct rev_info *revs, const char *name) { int num = ++revs->num_ignore_packed; @@ -915,6 +949,18 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->relative_date = 1; continue; } + if (!strncmp(arg, "--author=", 9)) { + add_header_grep(revs, "author", arg+9); + continue; + } + if (!strncmp(arg, "--committer=", 12)) { + add_header_grep(revs, "committer", arg+12); + continue; + } + if (!strncmp(arg, "--grep=", 7)) { + add_message_grep(revs, arg+7); + continue; + } opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i); if (opts > 0) { revs->diff = 1; @@ -975,6 +1021,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch if (diff_setup_done(&revs->diffopt) < 0) die("diff_setup_done failed"); + if (revs->header_filter) + compile_grep_patterns(revs->header_filter); + if (revs->message_filter) + compile_grep_patterns(revs->message_filter); + return left; } @@ -1049,10 +1100,33 @@ static void mark_boundary_to_show(struct commit *commit) static int commit_match(struct commit *commit, struct rev_info *opt) { + char *header, *message; + unsigned long header_len, message_len; + if (!opt->header_filter && !opt->message_filter) return 1; - /* match it here */ + header = commit->buffer; + message = strstr(header, "\n\n"); + if (message) { + message += 2; + header_len = message - header - 1; + message_len = strlen(message); + } + else { + header_len = strlen(header); + message = header; + message_len = 0; + } + + if (opt->header_filter && + !grep_buffer(opt->header_filter, NULL, header, header_len)) + return 0; + + if (opt->message_filter && + !grep_buffer(opt->message_filter, NULL, message, message_len)) + return 0; + return 1; } -- cgit v0.10.2-6-g49f6 From f69895fb0c5921f5b399f35a71caa9a023776ddf Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 18 Sep 2006 02:52:42 -0400 Subject: rev-list: fix segfault with --{author,committer,grep} We need to save the commit buffer if we're going to match against it. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 1f3333d..dbfee75 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -269,7 +269,9 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) revs.diff) usage(rev_list_usage); - save_commit_buffer = revs.verbose_header; + save_commit_buffer = revs.verbose_header || + revs.header_filter || + revs.message_filter; track_object_refs = 0; if (bisect_list) revs.limited = 1; -- cgit v0.10.2-6-g49f6 From a2ed6ae402582a3ee76e9b4639848eba261a12de Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 18 Sep 2006 10:07:51 -0700 Subject: git-log --author and --committer are not left-anchored by default I know that I'd prefer a rule where "--author=^Junio" would result in the grep-pattern being "^author Junio", but without the initial '^' it would be "^author .*Junio". Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano diff --git a/revision.c b/revision.c index 26dd418..bca1229 100644 --- a/revision.c +++ b/revision.c @@ -677,6 +677,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, static void add_header_grep(struct rev_info *revs, const char *field, const char *pattern) { char *pat; + const char *prefix; int patlen, fldlen; if (!revs->header_filter) { @@ -689,8 +690,13 @@ static void add_header_grep(struct rev_info *revs, const char *field, const char fldlen = strlen(field); patlen = strlen(pattern); - pat = xmalloc(patlen + fldlen + 3); - sprintf(pat, "^%s %s", field, pattern); + pat = xmalloc(patlen + fldlen + 10); + prefix = ".*"; + if (*pattern == '^') { + prefix = ""; + pattern++; + } + sprintf(pat, "^%s %s%s", field, prefix, pattern); append_grep_pattern(revs->header_filter, pat, "command line", 0, GREP_PATTERN); } -- cgit v0.10.2-6-g49f6 From cd0d74d2f9c7578b36e705dda55f79731dbe9696 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 18 Sep 2006 02:29:01 -0700 Subject: repack: use only pack-objects, not rev-list. Signed-off-by: Junio C Hamano diff --git a/git-repack.sh b/git-repack.sh index b525fc5..9ae5092 100755 --- a/git-repack.sh +++ b/git-repack.sh @@ -32,12 +32,10 @@ trap 'rm -f "$PACKTMP"-*' 0 1 2 3 15 # There will be more repacking strategies to come... case ",$all_into_one," in ,,) - rev_list='--unpacked' - pack_objects='--incremental' + args='--unpacked --incremental' ;; ,t,) - rev_list= - pack_objects= + args= # Redundancy check in all-into-one case is trivial. existing=`test -d "$PACKDIR" && cd "$PACKDIR" && \ @@ -45,11 +43,8 @@ case ",$all_into_one," in ;; esac -pack_objects="$pack_objects $local $quiet $no_reuse_delta$extra" -name=$( { git-rev-list --objects --all $rev_list || - echo "git-rev-list died with exit code $?" - } | - git-pack-objects --non-empty $pack_objects "$PACKTMP") || +args="$args $local $quiet $no_reuse_delta$extra" +name=$(git-pack-objects --non-empty --all $args Date: Tue, 19 Sep 2006 20:31:51 -0500 Subject: Add virtualization support to git-daemon Signed-off-by: Jon Loeliger Signed-off-by: Junio C Hamano diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index 741f2c6..51d7c94 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -11,6 +11,7 @@ SYNOPSIS 'git-daemon' [--verbose] [--syslog] [--inetd | --port=n] [--export-all] [--timeout=n] [--init-timeout=n] [--strict-paths] [--base-path=path] [--user-path | --user-path=path] + [--interpolated-path=pathtemplate] [--enable=service] [--disable=service] [--allow-override=service] [--forbid-override=service] [--reuseaddr] [--detach] [--pid-file=file] @@ -50,6 +51,12 @@ OPTIONS 'git://example.com/hello.git', `git-daemon` will interpret the path as '/srv/git/hello.git'. +--interpolated-path=pathtemplate:: + To support virtual hosting, an interpolated path template can be + used to dynamically construct alternate paths. The template + supports %H for the target hostname as supplied by the client, + and %D for the absolute path of the named repository. + --export-all:: Allow pulling from all directories that look like GIT repositories (have the 'objects' and 'refs' subdirectories), even if they @@ -135,6 +142,46 @@ upload-pack:: disable it by setting `daemon.uploadpack` configuration item to `false`. +EXAMPLES +-------- +git-daemon as inetd server:: + To set up `git-daemon` as an inetd service that handles any + repository under the whitelisted set of directories, /pub/foo + and /pub/bar, place an entry like the following into + /etc/inetd all on one line: ++ +------------------------------------------------ + git stream tcp nowait nobody /usr/bin/git-daemon + git-daemon --inetd --verbose + --syslog --export-all + /pub/foo /pub/bar +------------------------------------------------ + + +git-daemon as inetd server for virtual hosts:: + To set up `git-daemon` as an inetd service that handles + repositories for different virtual hosts, `www.example.com` + and `www.example.org`, place an entry like the following into + `/etc/inetd` all on one line: ++ +------------------------------------------------ + git stream tcp nowait nobody /usr/bin/git-daemon + git-daemon --inetd --verbose + --syslog --export-all + --interpolated-path=/pub/%H%D + /pub/www.example.org/software + /pub/www.example.com/software + /software +------------------------------------------------ ++ +In this example, the root-level directory `/pub` will contain +a subdirectory for each virtual host name supported. +Further, both hosts advertise repositories simply as +`git://www.example.com/software/repo.git`. For pre-1.4.0 +clients, a symlink from `/software` into the appropriate +default repository could be made as well. + + Author ------ Written by Linus Torvalds , YOSHIFUJI Hideaki diff --git a/Makefile b/Makefile index 8467447..fb2ade5 100644 --- a/Makefile +++ b/Makefile @@ -246,7 +246,9 @@ DIFF_OBJS = \ LIB_OBJS = \ blob.o commit.o connect.o csum-file.o cache-tree.o base85.o \ - date.o diff-delta.o entry.o exec_cmd.o ident.o lockfile.o \ + date.o diff-delta.o entry.o exec_cmd.o ident.o \ + interpolate.o \ + lockfile.o \ object.o pack-check.o patch-delta.o path.o pkt-line.o sideband.o \ quote.o read-cache.o refs.o run-command.o dir.o object-refs.o \ server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \ diff --git a/daemon.c b/daemon.c index a2954a0..eb4f3f1 100644 --- a/daemon.c +++ b/daemon.c @@ -12,6 +12,7 @@ #include "pkt-line.h" #include "cache.h" #include "exec_cmd.h" +#include "interpolate.h" static int log_syslog; static int verbose; @@ -21,6 +22,7 @@ static const char daemon_usage[] = "git-daemon [--verbose] [--syslog] [--inetd | --port=n] [--export-all]\n" " [--timeout=n] [--init-timeout=n] [--strict-paths]\n" " [--base-path=path] [--user-path | --user-path=path]\n" +" [--interpolated-path=path]\n" " [--reuseaddr] [--detach] [--pid-file=file]\n" " [--[enable|disable|allow-override|forbid-override]=service]\n" " [--user=user [[--group=group]] [directory...]"; @@ -34,6 +36,10 @@ static int export_all_trees; /* Take all paths relative to this one if non-NULL */ static char *base_path; +static char *interpolated_path; + +/* Flag indicating client sent extra args. */ +static int saw_extended_args; /* If defined, ~user notation is allowed and the string is inserted * after ~user/. E.g. a request to git://host/~alice/frotz would @@ -45,6 +51,21 @@ static const char *user_path; static unsigned int timeout; static unsigned int init_timeout; +/* + * Static table for now. Ugh. + * Feel free to make dynamic as needed. + */ +#define INTERP_SLOT_HOST (0) +#define INTERP_SLOT_DIR (1) +#define INTERP_SLOT_PERCENT (2) + +static struct interp interp_table[] = { + { "%H", 0}, + { "%D", 0}, + { "%%", "%"}, +}; + + static void logreport(int priority, const char *err, va_list params) { /* We should do a single write so that it is atomic and output @@ -152,10 +173,14 @@ static int avoid_alias(char *p) } } -static char *path_ok(char *dir) +static char *path_ok(struct interp *itable) { static char rpath[PATH_MAX]; + static char interp_path[PATH_MAX]; char *path; + char *dir; + + dir = itable[INTERP_SLOT_DIR].value; if (avoid_alias(dir)) { logerror("'%s': aliased", dir); @@ -184,16 +209,27 @@ static char *path_ok(char *dir) dir = rpath; } } + else if (interpolated_path && saw_extended_args) { + if (*dir != '/') { + /* Allow only absolute */ + logerror("'%s': Non-absolute path denied (interpolated-path active)", dir); + return NULL; + } + + interpolate(interp_path, PATH_MAX, interpolated_path, + interp_table, ARRAY_SIZE(interp_table)); + loginfo("Interpolated dir '%s'", interp_path); + + dir = interp_path; + } else if (base_path) { if (*dir != '/') { /* Allow only absolute */ logerror("'%s': Non-absolute path denied (base-path active)", dir); return NULL; } - else { - snprintf(rpath, PATH_MAX, "%s%s", base_path, dir); - dir = rpath; - } + snprintf(rpath, PATH_MAX, "%s%s", base_path, dir); + dir = rpath; } path = enter_repo(dir, strict_paths); @@ -257,12 +293,14 @@ static int git_daemon_config(const char *var, const char *value) return 0; } -static int run_service(char *dir, struct daemon_service *service) +static int run_service(struct interp *itable, struct daemon_service *service) { const char *path; int enabled = service->enabled; - loginfo("Request %s for '%s'", service->name, dir); + loginfo("Request %s for '%s'", + service->name, + itable[INTERP_SLOT_DIR].value); if (!enabled && !service->overridable) { logerror("'%s': service not enabled.", service->name); @@ -270,7 +308,7 @@ static int run_service(char *dir, struct daemon_service *service) return -1; } - if (!(path = path_ok(dir))) + if (!(path = path_ok(itable))) return -1; /* @@ -358,6 +396,28 @@ static void make_service_overridable(const char *name, int ena) { die("No such service %s", name); } +static void parse_extra_args(char *extra_args, int buflen) +{ + char *val; + int vallen; + char *end = extra_args + buflen; + + while (extra_args < end && *extra_args) { + saw_extended_args = 1; + if (strncasecmp("host=", extra_args, 5) == 0) { + val = extra_args + 5; + vallen = strlen(val) + 1; + if (*val) { + char *save = xmalloc(vallen); + interp_table[INTERP_SLOT_HOST].value = save; + strlcpy(save, val, vallen); + } + /* On to the next one */ + extra_args = val + vallen; + } + } +} + static int execute(struct sockaddr *addr) { static char line[1000]; @@ -398,13 +458,18 @@ static int execute(struct sockaddr *addr) if (len && line[len-1] == '\n') line[--len] = 0; + if (len != pktlen) + parse_extra_args(line + len + 1, pktlen - len - 1); + for (i = 0; i < ARRAY_SIZE(daemon_service); i++) { struct daemon_service *s = &(daemon_service[i]); int namelen = strlen(s->name); if (!strncmp("git-", line, 4) && !strncmp(s->name, line + 4, namelen) && - line[namelen + 4] == ' ') - return run_service(line + namelen + 5, s); + line[namelen + 4] == ' ') { + interp_table[INTERP_SLOT_DIR].value = line+namelen+5; + return run_service(interp_table, s); + } } logerror("Protocol error: '%s'", line); @@ -867,6 +932,10 @@ int main(int argc, char **argv) base_path = arg+12; continue; } + if (!strncmp(arg, "--interpolated-path=", 20)) { + interpolated_path = arg+20; + continue; + } if (!strcmp(arg, "--reuseaddr")) { reuseaddr = 1; continue; diff --git a/interpolate.c b/interpolate.c new file mode 100644 index 0000000..d82f1b5 --- /dev/null +++ b/interpolate.c @@ -0,0 +1,82 @@ +/* + * Copyright 2006 Jon Loeliger + */ + +#include + +#include "interpolate.h" + + +/* + * Convert a NUL-terminated string in buffer orig + * into the supplied buffer, result, whose length is reslen, + * performing substitutions on %-named sub-strings from + * the table, interps, with ninterps entries. + * + * Example interps: + * { + * { "%H", "example.org"}, + * { "%port", "123"}, + * { "%%", "%"}, + * } + * + * Returns 1 on a successful substitution pass that fits in result, + * Returns 0 on a failed or overflowing substitution pass. + */ + +int interpolate(char *result, int reslen, + char *orig, + struct interp *interps, int ninterps) +{ + char *src = orig; + char *dest = result; + int newlen = 0; + char *name, *value; + int namelen, valuelen; + int i; + char c; + + memset(result, 0, reslen); + + while ((c = *src) && newlen < reslen - 1) { + if (c == '%') { + /* Try to match an interpolation string. */ + for (i = 0; i < ninterps; i++) { + name = interps[i].name; + namelen = strlen(name); + if (strncmp(src, name, namelen) == 0) { + break; + } + } + + /* Check for valid interpolation. */ + if (i < ninterps) { + value = interps[i].value; + valuelen = strlen(value); + + if (newlen + valuelen < reslen - 1) { + /* Substitute. */ + strncpy(dest, value, valuelen); + newlen += valuelen; + dest += valuelen; + src += namelen; + } else { + /* Something's not fitting. */ + return 0; + } + + } else { + /* Skip bogus interpolation. */ + *dest++ = *src++; + newlen++; + } + + } else { + /* Straight copy one non-interpolation character. */ + *dest++ = *src++; + newlen++; + } + } + + return newlen < reslen - 1; +} diff --git a/interpolate.h b/interpolate.h new file mode 100644 index 0000000..00c63a5 --- /dev/null +++ b/interpolate.h @@ -0,0 +1,18 @@ +/* + * Copyright 2006 Jon Loeliger + */ + +#ifndef INTERPOLATE_H +#define INTERPOLATE_H + + +struct interp { + char *name; + char *value; +}; + +extern int interpolate(char *result, int reslen, + char *orig, + struct interp *interps, int ninterps); + +#endif /* INTERPOLATE_H */ -- cgit v0.10.2-6-g49f6 From 480c1ca6fd8df58a783e231648b489ed2bfd17f1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 20 Sep 2006 12:39:46 -0700 Subject: Update grep internal for grepping only in head/body This further updates the built-in grep engine so that we can say something like "this pattern should match only in head". This can be used to simplify grepping in the log messages. Signed-off-by: Junio C Hamano diff --git a/grep.c b/grep.c index 61db6e1..cc8d684 100644 --- a/grep.c +++ b/grep.c @@ -43,6 +43,8 @@ static struct grep_expr *compile_pattern_atom(struct grep_pat **list) p = *list; switch (p->token) { case GREP_PATTERN: /* atom */ + case GREP_PATTERN_HEAD: + case GREP_PATTERN_BODY: x = xcalloc(1, sizeof (struct grep_expr)); x->node = GREP_NODE_ATOM; x->u.atom = p; @@ -141,10 +143,16 @@ void compile_grep_patterns(struct grep_opt *opt) /* First compile regexps */ for (p = opt->pattern_list; p; p = p->next) { - if (p->token == GREP_PATTERN) + switch (p->token) { + case GREP_PATTERN: /* atom */ + case GREP_PATTERN_HEAD: + case GREP_PATTERN_BODY: compile_regexp(p, opt); - else + break; + default: opt->extended = 1; + break; + } } if (!opt->extended) @@ -210,12 +218,16 @@ static int fixmatch(const char *pattern, char *line, regmatch_t *match) } } -static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol) +static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol, enum grep_context ctx) { int hit = 0; int at_true_bol = 1; regmatch_t pmatch[10]; + if ((p->token != GREP_PATTERN) && + ((p->token == GREP_PATTERN_HEAD) != (ctx == GREP_CONTEXT_HEAD))) + return 0; + again: if (!opt->fixed) { regex_t *exp = &p->regexp; @@ -262,37 +274,40 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol static int match_expr_eval(struct grep_opt *opt, struct grep_expr *x, - char *bol, char *eol) + char *bol, char *eol, + enum grep_context ctx) { switch (x->node) { case GREP_NODE_ATOM: - return match_one_pattern(opt, x->u.atom, bol, eol); + return match_one_pattern(opt, x->u.atom, bol, eol, ctx); break; case GREP_NODE_NOT: - return !match_expr_eval(opt, x->u.unary, bol, eol); + return !match_expr_eval(opt, x->u.unary, bol, eol, ctx); case GREP_NODE_AND: - return (match_expr_eval(opt, x->u.binary.left, bol, eol) && - match_expr_eval(opt, x->u.binary.right, bol, eol)); + return (match_expr_eval(opt, x->u.binary.left, bol, eol, ctx) && + match_expr_eval(opt, x->u.binary.right, bol, eol, ctx)); case GREP_NODE_OR: - return (match_expr_eval(opt, x->u.binary.left, bol, eol) || - match_expr_eval(opt, x->u.binary.right, bol, eol)); + return (match_expr_eval(opt, x->u.binary.left, bol, eol, ctx) || + match_expr_eval(opt, x->u.binary.right, bol, eol, ctx)); } die("Unexpected node type (internal error) %d\n", x->node); } -static int match_expr(struct grep_opt *opt, char *bol, char *eol) +static int match_expr(struct grep_opt *opt, char *bol, char *eol, + enum grep_context ctx) { struct grep_expr *x = opt->pattern_expression; - return match_expr_eval(opt, x, bol, eol); + return match_expr_eval(opt, x, bol, eol, ctx); } -static int match_line(struct grep_opt *opt, char *bol, char *eol) +static int match_line(struct grep_opt *opt, char *bol, char *eol, + enum grep_context ctx) { struct grep_pat *p; if (opt->extended) - return match_expr(opt, bol, eol); + return match_expr(opt, bol, eol, ctx); for (p = opt->pattern_list; p; p = p->next) { - if (match_one_pattern(opt, p, bol, eol)) + if (match_one_pattern(opt, p, bol, eol, ctx)) return 1; } return 0; @@ -312,6 +327,7 @@ int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long int binary_match_only = 0; const char *hunk_mark = ""; unsigned count = 0; + enum grep_context ctx = GREP_CONTEXT_HEAD; if (buffer_is_binary(buf, size)) { switch (opt->binary) { @@ -339,7 +355,10 @@ int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long ch = *eol; *eol = 0; - hit = match_line(opt, bol, eol); + if ((ctx == GREP_CONTEXT_HEAD) && (eol == bol)) + ctx = GREP_CONTEXT_BODY; + + hit = match_line(opt, bol, eol, ctx); *eol = ch; /* "grep -v -e foo -e bla" should list lines diff --git a/grep.h b/grep.h index 80122b0..0b503ea 100644 --- a/grep.h +++ b/grep.h @@ -3,6 +3,8 @@ enum grep_pat_token { GREP_PATTERN, + GREP_PATTERN_HEAD, + GREP_PATTERN_BODY, GREP_AND, GREP_OPEN_PAREN, GREP_CLOSE_PAREN, @@ -10,6 +12,11 @@ enum grep_pat_token { GREP_OR, }; +enum grep_context { + GREP_CONTEXT_HEAD, + GREP_CONTEXT_BODY, +}; + struct grep_pat { struct grep_pat *next; const char *origin; -- cgit v0.10.2-6-g49f6 From 2d10c555374df257e32848ba6f77fd73d608645f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 20 Sep 2006 13:21:56 -0700 Subject: git log: Unify header_filter and message_filter into one. Now we can tell the built-in grep to grep only in head or in body, use that to update --author, --committer, and --grep. Unfortunately, to make --and, --not and other grep boolean expressions useful, as in: # Things written by Junio committed and by Linus and log # does not talk about diff. git log --author=Junio --and --committer=Linus \ --grep-not --grep=diff we will need to do another round of built-in grep core enhancement, because grep boolean expressions are designed to work on one line at a time. Signed-off-by: Junio C Hamano diff --git a/builtin-rev-list.c b/builtin-rev-list.c index dbfee75..fb7fc92 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -269,9 +269,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) revs.diff) usage(rev_list_usage); - save_commit_buffer = revs.verbose_header || - revs.header_filter || - revs.message_filter; + save_commit_buffer = revs.verbose_header || revs.grep_filter; track_object_refs = 0; if (bisect_list) revs.limited = 1; diff --git a/revision.c b/revision.c index bca1229..93f2513 100644 --- a/revision.c +++ b/revision.c @@ -674,19 +674,24 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, return 0; } -static void add_header_grep(struct rev_info *revs, const char *field, const char *pattern) +static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token what) { - char *pat; - const char *prefix; - int patlen, fldlen; - - if (!revs->header_filter) { + if (!revs->grep_filter) { struct grep_opt *opt = xcalloc(1, sizeof(*opt)); opt->status_only = 1; opt->pattern_tail = &(opt->pattern_list); opt->regflags = REG_NEWLINE; - revs->header_filter = opt; + revs->grep_filter = opt; } + append_grep_pattern(revs->grep_filter, ptn, + "command line", 0, what); +} + +static void add_header_grep(struct rev_info *revs, const char *field, const char *pattern) +{ + char *pat; + const char *prefix; + int patlen, fldlen; fldlen = strlen(field); patlen = strlen(pattern); @@ -697,21 +702,12 @@ static void add_header_grep(struct rev_info *revs, const char *field, const char pattern++; } sprintf(pat, "^%s %s%s", field, prefix, pattern); - append_grep_pattern(revs->header_filter, pat, - "command line", 0, GREP_PATTERN); + add_grep(revs, pat, GREP_PATTERN_HEAD); } static void add_message_grep(struct rev_info *revs, const char *pattern) { - if (!revs->message_filter) { - struct grep_opt *opt = xcalloc(1, sizeof(*opt)); - opt->status_only = 1; - opt->pattern_tail = &(opt->pattern_list); - opt->regflags = REG_NEWLINE; - revs->message_filter = opt; - } - append_grep_pattern(revs->message_filter, pattern, - "command line", 0, GREP_PATTERN); + add_grep(revs, pattern, GREP_PATTERN_BODY); } static void add_ignore_packed(struct rev_info *revs, const char *name) @@ -955,6 +951,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->relative_date = 1; continue; } + + /* + * Grepping the commit log + */ if (!strncmp(arg, "--author=", 9)) { add_header_grep(revs, "author", arg+9); continue; @@ -967,6 +967,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch add_message_grep(revs, arg+7); continue; } + opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i); if (opts > 0) { revs->diff = 1; @@ -1027,10 +1028,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch if (diff_setup_done(&revs->diffopt) < 0) die("diff_setup_done failed"); - if (revs->header_filter) - compile_grep_patterns(revs->header_filter); - if (revs->message_filter) - compile_grep_patterns(revs->message_filter); + if (revs->grep_filter) + compile_grep_patterns(revs->grep_filter); return left; } @@ -1106,34 +1105,11 @@ static void mark_boundary_to_show(struct commit *commit) static int commit_match(struct commit *commit, struct rev_info *opt) { - char *header, *message; - unsigned long header_len, message_len; - - if (!opt->header_filter && !opt->message_filter) + if (!opt->grep_filter) return 1; - - header = commit->buffer; - message = strstr(header, "\n\n"); - if (message) { - message += 2; - header_len = message - header - 1; - message_len = strlen(message); - } - else { - header_len = strlen(header); - message = header; - message_len = 0; - } - - if (opt->header_filter && - !grep_buffer(opt->header_filter, NULL, header, header_len)) - return 0; - - if (opt->message_filter && - !grep_buffer(opt->message_filter, NULL, message, message_len)) - return 0; - - return 1; + return grep_buffer(opt->grep_filter, + NULL, /* we say nothing, not even filename */ + commit->buffer, strlen(commit->buffer)); } struct commit *get_revision(struct rev_info *revs) diff --git a/revision.h b/revision.h index 60030e5..3adab95 100644 --- a/revision.h +++ b/revision.h @@ -72,8 +72,7 @@ struct rev_info { const char *extra_headers; /* Filter by commit log message */ - struct grep_opt *header_filter; - struct grep_opt *message_filter; + struct grep_opt *grep_filter; /* special limits */ int max_count; -- cgit v0.10.2-6-g49f6 From e49521b56d8715f46b93ee6bc95f7de9c6858365 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 20 Sep 2006 16:04:46 -0700 Subject: Make hexval() available to others. builtin-mailinfo.c has its own hexval implementaiton but it can share the table-lookup one recently implemented in sha1_file.c Signed-off-by: Junio C Hamano diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index 0c65f93..b8d7dbc 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -451,17 +451,6 @@ static int read_one_header_line(char *line, int sz, FILE *in) return ofs; } -static unsigned hexval(int c) -{ - if (c >= '0' && c <= '9') - return c - '0'; - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - return ~0; -} - static int decode_q_segment(char *in, char *ot, char *ep, int rfc2047) { int c; diff --git a/cache.h b/cache.h index 57db7c9..d557e75 100644 --- a/cache.h +++ b/cache.h @@ -278,6 +278,12 @@ enum object_type { OBJ_BAD, }; +extern signed char hexval_table[256]; +static inline unsigned int hexval(unsigned int c) +{ + return hexval_table[c]; +} + /* Convert to/from hex/sha1 representation */ #define MINIMUM_ABBREV 4 #define DEFAULT_ABBREV 7 diff --git a/sha1_file.c b/sha1_file.c index b89edb9..0f9c2b6 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -26,44 +26,40 @@ const unsigned char null_sha1[20]; static unsigned int sha1_file_open_flag = O_NOATIME; -static inline unsigned int hexval(unsigned int c) -{ - static signed char val[256] = { - -1, -1, -1, -1, -1, -1, -1, -1, /* 00-07 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 08-0f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 10-17 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 18-1f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 20-27 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 28-2f */ - 0, 1, 2, 3, 4, 5, 6, 7, /* 30-37 */ - 8, 9, -1, -1, -1, -1, -1, -1, /* 38-3f */ - -1, 10, 11, 12, 13, 14, 15, -1, /* 40-47 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 48-4f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 50-57 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 58-5f */ - -1, 10, 11, 12, 13, 14, 15, -1, /* 60-67 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 68-67 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 70-77 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 78-7f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 80-87 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 88-8f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 90-97 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 98-9f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* a0-a7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* a8-af */ - -1, -1, -1, -1, -1, -1, -1, -1, /* b0-b7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* b8-bf */ - -1, -1, -1, -1, -1, -1, -1, -1, /* c0-c7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* c8-cf */ - -1, -1, -1, -1, -1, -1, -1, -1, /* d0-d7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* d8-df */ - -1, -1, -1, -1, -1, -1, -1, -1, /* e0-e7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* e8-ef */ - -1, -1, -1, -1, -1, -1, -1, -1, /* f0-f7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* f8-ff */ - }; - return val[c]; -} +signed char hexval_table[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, /* 00-07 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 08-0f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 10-17 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 18-1f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 20-27 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 28-2f */ + 0, 1, 2, 3, 4, 5, 6, 7, /* 30-37 */ + 8, 9, -1, -1, -1, -1, -1, -1, /* 38-3f */ + -1, 10, 11, 12, 13, 14, 15, -1, /* 40-47 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 48-4f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 50-57 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 58-5f */ + -1, 10, 11, 12, 13, 14, 15, -1, /* 60-67 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 68-67 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 70-77 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 78-7f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 80-87 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 88-8f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 90-97 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* 98-9f */ + -1, -1, -1, -1, -1, -1, -1, -1, /* a0-a7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* a8-af */ + -1, -1, -1, -1, -1, -1, -1, -1, /* b0-b7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* b8-bf */ + -1, -1, -1, -1, -1, -1, -1, -1, /* c0-c7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* c8-cf */ + -1, -1, -1, -1, -1, -1, -1, -1, /* d0-d7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* d8-df */ + -1, -1, -1, -1, -1, -1, -1, -1, /* e0-e7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* e8-ef */ + -1, -1, -1, -1, -1, -1, -1, -1, /* f0-f7 */ + -1, -1, -1, -1, -1, -1, -1, -1, /* f8-ff */ +}; int get_sha1_hex(const char *hex, unsigned char *sha1) { -- cgit v0.10.2-6-g49f6 From 7dd45e15c259e44b1c8b5ffdfc0c3d002c7f642c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 20 Sep 2006 16:11:08 -0700 Subject: sha1_name.c: understand "describe" output as a valid object name Signed-off-by: Junio C Hamano diff --git a/sha1_name.c b/sha1_name.c index 1fbc443..9b226e3 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -431,6 +431,26 @@ static int peel_onion(const char *name, int len, unsigned char *sha1) return 0; } +static int get_describe_name(const char *name, int len, unsigned char *sha1) +{ + const char *cp; + + for (cp = name + len - 1; name + 2 <= cp; cp--) { + char ch = *cp; + if (hexval(ch) & ~0377) { + /* We must be looking at g in "SOMETHING-g" + * for it to be describe output. + */ + if (ch == 'g' && cp[-1] == '-') { + cp++; + len -= cp - name; + return get_short_sha1(cp, len, sha1, 1); + } + } + } + return -1; +} + static int get_sha1_1(const char *name, int len, unsigned char *sha1) { int ret, has_suffix; @@ -472,6 +492,12 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1) ret = get_sha1_basic(name, len, sha1); if (!ret) return 0; + + /* It could be describe output that is "SOMETHING-gXXXX" */ + ret = get_describe_name(name, len, sha1); + if (!ret) + return 0; + return get_short_sha1(name, len, sha1, 0); } -- cgit v0.10.2-6-g49f6 From 11031d7e9f34f6a20ff4a4bd4fa3e5e3c0024a57 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 21 Sep 2006 01:07:54 +0200 Subject: add receive.denyNonFastforwards config variable If receive.denyNonFastforwards is set to true, git-receive-pack will deny non fast-forwards, i.e. forced updates. Most notably, a push to a repository which has that flag set will fail. As a first user, 'git-init-db --shared' sets this flag, since in a shared setup, you are most unlikely to want forced pushes to succeed. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/builtin-init-db.c b/builtin-init-db.c index 5085018..c3ed1ce 100644 --- a/builtin-init-db.c +++ b/builtin-init-db.c @@ -311,6 +311,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) */ sprintf(buf, "%d", shared_repository); git_config_set("core.sharedrepository", buf); + git_config_set("receive.denyNonFastforwards", "true"); } return 0; diff --git a/cache.h b/cache.h index d557e75..ef2e581 100644 --- a/cache.h +++ b/cache.h @@ -188,6 +188,7 @@ extern int prefer_symlink_refs; extern int log_all_ref_updates; extern int warn_ambiguous_refs; extern int shared_repository; +extern int deny_non_fast_forwards; extern const char *apply_default_whitespace; extern int zlib_compression_level; diff --git a/environment.c b/environment.c index 84d870c..63b1d15 100644 --- a/environment.c +++ b/environment.c @@ -20,6 +20,7 @@ int warn_ambiguous_refs = 1; int repository_format_version; char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8"; int shared_repository = PERM_UMASK; +int deny_non_fast_forwards = 0; const char *apply_default_whitespace; int zlib_compression_level = Z_DEFAULT_COMPRESSION; int pager_in_use; diff --git a/receive-pack.c b/receive-pack.c index 78f75da..a6ec9f9 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -2,6 +2,8 @@ #include "refs.h" #include "pkt-line.h" #include "run-command.h" +#include "commit.h" +#include "object.h" static const char receive_pack_usage[] = "git-receive-pack "; @@ -127,6 +129,20 @@ static int update(struct command *cmd) return error("unpack should have generated %s, " "but I can't find it!", new_hex); } + if (deny_non_fast_forwards && !is_null_sha1(old_sha1)) { + struct commit *old_commit, *new_commit; + struct commit_list *bases; + + old_commit = (struct commit *)parse_object(old_sha1); + new_commit = (struct commit *)parse_object(new_sha1); + for (bases = get_merge_bases(old_commit, new_commit, 1); + bases; bases = bases->next) + if (!hashcmp(old_sha1, bases->item->object.sha1)) + break; + if (!bases) + return error("denying non-fast forward;" + " you should pull first"); + } safe_create_leading_directories(lock_name); newfd = open(lock_name, O_CREAT | O_EXCL | O_WRONLY, 0666); diff --git a/setup.c b/setup.c index 2afdba4..9a46a58 100644 --- a/setup.c +++ b/setup.c @@ -244,6 +244,8 @@ int check_repository_format_version(const char *var, const char *value) repository_format_version = git_config_int(var, value); else if (strcmp(var, "core.sharedrepository") == 0) shared_repository = git_config_perm(var, value); + else if (strcmp(var, "receive.denynonfastforwards") == 0) + deny_non_fast_forwards = git_config_bool(var, value); return 0; } -- cgit v0.10.2-6-g49f6 From 9edd7e4652e080a1a3b1ef614d22eba75b39ef87 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 21 Sep 2006 02:07:19 +0200 Subject: receive-pack: plug memory leak in fast-forward checking code. Signed-off-by: Junio C Hamano diff --git a/receive-pack.c b/receive-pack.c index a6ec9f9..ea2dbd4 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -131,17 +131,18 @@ static int update(struct command *cmd) } if (deny_non_fast_forwards && !is_null_sha1(old_sha1)) { struct commit *old_commit, *new_commit; - struct commit_list *bases; + struct commit_list *bases, *ent; old_commit = (struct commit *)parse_object(old_sha1); new_commit = (struct commit *)parse_object(new_sha1); - for (bases = get_merge_bases(old_commit, new_commit, 1); - bases; bases = bases->next) - if (!hashcmp(old_sha1, bases->item->object.sha1)) + bases = get_merge_bases(old_commit, new_commit, 1); + for (ent = bases; ent; ent = ent->next) + if (!hashcmp(old_sha1, ent->item->object.sha1)) break; - if (!bases) + free_commit_list(bases); + if (!ent) return error("denying non-fast forward;" - " you should pull first"); + " you should pull first"); } safe_create_leading_directories(lock_name); -- cgit v0.10.2-6-g49f6 From 199a92186b6721b23a2400c91f8bd44e7ffa349a Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 21 Sep 2006 02:10:30 +0200 Subject: Document receive.denyNonFastforwards [jc: with a fix to config handling in t5400 test, which took annoyingly long to diagnose.] Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/Documentation/config.txt b/Documentation/config.txt index 844cae4..bb2fbc3 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -267,3 +267,10 @@ whatchanged.difftree:: imap:: The configuration variables in the 'imap' section are described in gitlink:git-imap-send[1]. + +receive.denyNonFastforwads:: + If set to true, git-receive-pack will deny a ref update which is + not a fast forward. Use this to prevent such an update via a push, + even if that push is forced. This configuration variable is + set when initializing a shared repository. + diff --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt index 63cd5da..ca7d09d 100644 --- a/Documentation/git-init-db.txt +++ b/Documentation/git-init-db.txt @@ -48,6 +48,10 @@ is given: - 'all' (or 'world' or 'everybody'): Same as 'group', but make the repository readable by all users. +By default, the configuration flag receive.denyNonFastforward is enabled +in shared repositories, so that you cannot force a non fast-forwarding push +into it. + -- diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt index f9457d4..0dfadc2 100644 --- a/Documentation/git-receive-pack.txt +++ b/Documentation/git-receive-pack.txt @@ -73,6 +73,8 @@ packed and is served via a dumb transport. There are other real-world examples of using update and post-update hooks found in the Documentation/howto directory. +git-receive-pack honours the receive.denyNonFastforwards flag, which +tells it if updates to a ref should be denied if they are not fast-forwards. OPTIONS ------- diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index f3694ac..8afb899 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -64,4 +64,18 @@ test_expect_success \ cmp victim/.git/refs/heads/master .git/refs/heads/master ' +unset GIT_CONFIG GIT_CONFIG_LOCAL +HOME=`pwd`/no-such-directory +export HOME ;# this way we force the victim/.git/config to be used. + +test_expect_success \ + 'pushing with --force should be denied with denyNonFastforwards' ' + cd victim && + git-repo-config receive.denyNonFastforwards true && + cd .. && + git-update-ref refs/heads/master master^ && + git-send-pack --force ./victim/.git/ master && + ! diff -u .git/refs/heads/master victim/.git/refs/heads/master +' + test_done -- cgit v0.10.2-6-g49f6 From 053d62bb5bd523f492c6ef2e202da837b7f56905 Mon Sep 17 00:00:00 2001 From: Martin Waitz Date: Thu, 21 Sep 2006 09:48:21 +0200 Subject: gitweb: fix display of trees via PATH_INFO. When adding a / to the URL, git should display the corresponding tree object, but it has to remove the / first. Signed-off-by: Martin Waitz Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index baadbe7..ea57717 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -300,6 +300,7 @@ sub evaluate_path_info { $pathname =~ s,^/+,,; if (!$pathname || substr($pathname, -1) eq "/") { $action ||= "tree"; + $pathname =~ s,/$,,; } else { $action ||= "blob_plain"; } -- cgit v0.10.2-6-g49f6 From 16fdb4882e3f7b5b60907a2729df494aaa1410a3 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Thu, 21 Sep 2006 02:05:50 +0200 Subject: Fix showing of path in tree view This patch fixes two things - links to all path elements except the last one were broken since gitweb does not like the trailing slash in them, and the root tree was not reachable from the subdirectory view. To compensate for the one more slash in the front, the trailing slash is not there anymore. ;-) I don't care if it stays there though. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index ea57717..fb8d37e 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1514,12 +1514,15 @@ sub git_print_page_path { my $fullname = ''; print "
"; + print $cgi->a({-href => href(action=>"tree", hash_base=>$hb), + -title => '/'}, '/'); + print " "; foreach my $dir (@dirname) { - $fullname .= $dir . '/'; + $fullname .= ($fullname ? '/' : '') . $dir; print $cgi->a({-href => href(action=>"tree", file_name=>$fullname, hash_base=>$hb), - -title => $fullname}, esc_html($dir)); - print "/"; + -title => $fullname}, esc_html($dir . '/')); + print " "; } if (defined $type && $type eq 'blob') { print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name, @@ -1528,8 +1531,7 @@ sub git_print_page_path { } elsif (defined $type && $type eq 'tree') { print $cgi->a({-href => href(action=>"tree", file_name=>$file_name, hash_base=>$hb), - -title => $name}, esc_html($basename)); - print "/"; + -title => $name}, esc_html($basename . '/')); } else { print esc_html($basename); } -- cgit v0.10.2-6-g49f6 From 1d782b03b078c1a525cbb5728f6dc5eb06f06924 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Thu, 21 Sep 2006 18:09:12 +0200 Subject: gitweb: Make git_get_hash_by_path check type if provided Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index fb8d37e..d3757f4 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -718,6 +718,7 @@ sub git_get_project_config { sub git_get_hash_by_path { my $base = shift; my $path = shift || return undef; + my $type = shift; my $tree = $base; @@ -728,6 +729,10 @@ sub git_get_hash_by_path { #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; + if (defined $type && $type ne $2) { + # type doesn't match + return undef; + } return $3; } -- cgit v0.10.2-6-g49f6 From 609ff267fb03fb10dcefd15fc1f0ef3d7a1ba5ce Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Fri, 22 Sep 2006 01:58:40 +0200 Subject: gitweb: Link (HEAD) tree for each project from projects list Current projects list is oriented on easily getting "what's new" information. But when already using gitweb as an interface to something, I personally find myself to _much_ more frequently wanting to rather see "what's in" (or "what's new in") and it's quite annoying to have to go through the summary page (which is also rather expensive to generate) just to get there. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index d3757f4..8fd7f66 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2282,7 +2282,8 @@ sub git_project_list { "
\n" . "\n"; } -- cgit v0.10.2-6-g49f6 From cae1862a3b55b487731e9857f2213ac59d5646d1 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Fri, 22 Sep 2006 03:19:41 +0200 Subject: gitweb: More per-view navigation bar links Navigation bars in various views were empty or missed important items that should have been there, e.g. getting a snapshot in tree view or log of ancestry in commit view... This feeble patch attempts to consolidate that. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 8fd7f66..8ce77f6 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2441,6 +2441,9 @@ sub git_blame2 { $cgi->a({-href => href(action=>"blob", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, "blob") . " | " . + $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, + "history") . + " | " . $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, "head"); git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); @@ -2507,6 +2510,9 @@ sub git_blame { $cgi->a({-href => href(action=>"blob", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, "blob") . " | " . + $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, + "history") . + " | " . $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, "head"); git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); @@ -2682,6 +2688,10 @@ sub git_blob { " | "; } $formats_nav .= + $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, + hash=>$hash, file_name=>$file_name)}, + "history") . + " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$hash, file_name=>$file_name)}, "plain") . @@ -2717,6 +2727,9 @@ sub git_blob { } sub git_tree { + my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot'); + my $have_snapshot = (defined $ctype && defined $suffix); + if (!defined $hash) { $hash = git_get_head_hash($project); if (defined $file_name) { @@ -2740,7 +2753,23 @@ sub git_tree { my $base = ""; my ($have_blame) = gitweb_check_feature('blame'); if (defined $hash_base && (my %co = parse_commit($hash_base))) { - git_print_page_nav('tree','', $hash_base); + my @views_nav = (); + if (defined $file_name) { + push @views_nav, + $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, + hash=>$hash, file_name=>$file_name)}, + "history"), + $cgi->a({-href => href(action=>"tree", + hash_base=>"HEAD", file_name=>$file_name)}, + "head"); + } + if ($have_snapshot) { + # FIXME: Should be available when we have no hash base as well. + push @views_nav, + $cgi->a({-href => href(action=>"snapshot")}, + "snapshot"); + } + git_print_page_nav('tree','', $hash_base, undef, undef, join(' | ', @views_nav)); git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base); } else { undef $hash_base; @@ -2885,17 +2914,22 @@ sub git_commit { my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot'); my $have_snapshot = (defined $ctype && defined $suffix); - my $formats_nav = ''; + my @views_nav = (); if (defined $file_name && defined $co{'parent'}) { my $parent = $co{'parent'}; - $formats_nav .= + push @views_nav, $cgi->a({-href => href(action=>"blame", hash_parent=>$parent, file_name=>$file_name)}, "blame"); } + if (defined $co{'parent'}) { + push @views_nav, + $cgi->a({-href => href(action=>"shortlog", hash=>$hash)}, "shortlog"), + $cgi->a({-href => href(action=>"log", hash=>$hash)}, "log"); + } git_header_html(undef, $expires); git_print_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff', $hash, $co{'tree'}, $hash, - $formats_nav); + join (' | ', @views_nav)); if (defined $co{'parent'}) { git_print_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash); -- cgit v0.10.2-6-g49f6 From 35749ae566b15d1860cbfba5bc5ac227eb785715 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Fri, 22 Sep 2006 03:19:44 +0200 Subject: gitweb: Link to tree instead of snapshot in shortlog Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 8ce77f6..cbbd75c 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1975,9 +1975,6 @@ sub git_shortlog_body { # uses global variable $project my ($revlist, $from, $to, $refs, $extra) = @_; - my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot'); - my $have_snapshot = (defined $ctype && defined $suffix); - $from = 0 unless defined $from; $to = $#{$revlist} if (!defined $to || $#{$revlist} < $to); @@ -2003,10 +2000,8 @@ sub git_shortlog_body { print "\n" . "\n" . "\n"; } -- cgit v0.10.2-6-g49f6 From 1d62be25ed931f1892fad8639037c99677db5d1d Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Fri, 22 Sep 2006 03:19:46 +0200 Subject: gitweb: Link to latest tree from the head line in heads list Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index cbbd75c..0091e18 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2163,7 +2163,8 @@ sub git_heads_body { "\n" . "\n" . ""; } -- cgit v0.10.2-6-g49f6 From 6ef4cb2e8dd791612044f5e71f61a4788e87c4ac Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Fri, 22 Sep 2006 03:19:48 +0200 Subject: gitweb: Link to associated tree from a particular log item in full log view Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 0091e18..34ef3fc 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2870,6 +2870,8 @@ sub git_log { $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " . $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . + " | " . + $cgi->a({-href => href(action=>"tree", hash=>$commit), hash_base=>$commit}, "tree") . "
\n" . "\n" . "" . esc_html($co{'author_name'}) . " [$ad{'rfc2822'}]
\n" . -- cgit v0.10.2-6-g49f6 From 35329cc1ccd8c720628a72276402d5c3788b48e7 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Fri, 22 Sep 2006 03:19:50 +0200 Subject: gitweb: Rename "plain" labels to "raw" I don't have much preference either way and as far as I'm concerned, it may go the other way as well. Consistency is what is important. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 34ef3fc..8b4d34f 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2690,14 +2690,14 @@ sub git_blob { " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$hash, file_name=>$file_name)}, - "plain") . + "raw") . " | " . $cgi->a({-href => href(action=>"blob", hash_base=>"HEAD", file_name=>$file_name)}, "head"); } else { $formats_nav .= - $cgi->a({-href => href(action=>"blob_plain", hash=>$hash)}, "plain"); + $cgi->a({-href => href(action=>"blob_plain", hash=>$hash)}, "raw"); } git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); @@ -3106,7 +3106,7 @@ sub git_blobdiff { hash=>$hash, hash_parent=>$hash_parent, hash_base=>$hash_base, hash_parent_base=>$hash_parent_base, file_name=>$file_name, file_parent=>$file_parent)}, - "plain"); + "raw"); git_header_html(undef, $expires); if (defined $hash_base && (my %co = parse_commit($hash_base))) { git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); @@ -3209,7 +3209,7 @@ sub git_commitdiff { my $formats_nav = $cgi->a({-href => href(action=>"commitdiff_plain", hash=>$hash, hash_parent=>$hash_parent)}, - "plain"); + "raw"); git_header_html(undef, $expires); git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav); -- cgit v0.10.2-6-g49f6 From f35274dad8e617c1ea2c55c2b7b0fbbcb1abd8ae Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Fri, 22 Sep 2006 03:19:53 +0200 Subject: gitweb: Relabel "head" as "HEAD" "head" is a reference in refs/heads/, while those labels mean HEAD, the latest revision of the default branch. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 8b4d34f..1ce4973 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2441,7 +2441,7 @@ sub git_blame2 { "history") . " | " . $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, - "head"); + "HEAD"); git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); git_print_page_path($file_name, $ftype, $hash_base); @@ -2510,7 +2510,7 @@ sub git_blame { "history") . " | " . $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, - "head"); + "HEAD"); git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); git_print_page_path($file_name, 'blob', $hash_base); @@ -2694,7 +2694,7 @@ sub git_blob { " | " . $cgi->a({-href => href(action=>"blob", hash_base=>"HEAD", file_name=>$file_name)}, - "head"); + "HEAD"); } else { $formats_nav .= $cgi->a({-href => href(action=>"blob_plain", hash=>$hash)}, "raw"); @@ -2757,7 +2757,7 @@ sub git_tree { "history"), $cgi->a({-href => href(action=>"tree", hash_base=>"HEAD", file_name=>$file_name)}, - "head"); + "HEAD"), } if ($have_snapshot) { # FIXME: Should be available when we have no hash base as well. -- cgit v0.10.2-6-g49f6 From 26d0a976e88cf2e1ccb0b4d8172e9ce24d144d1f Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sat, 23 Sep 2006 01:00:12 +0200 Subject: Make path in tree view look nicer Based on talk on the IRC with Junio some evenings ago, I've updated the path showing in tree view to look better and sent updated patches privately, but it seems the old version ended up being used, so here's the new one again. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 1ce4973..b9df3cc 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1520,14 +1520,14 @@ sub git_print_page_path { print "
"; print $cgi->a({-href => href(action=>"tree", hash_base=>$hb), - -title => '/'}, '/'); - print " "; + -title => 'tree root'}, "[$project]"); + print " / "; foreach my $dir (@dirname) { $fullname .= ($fullname ? '/' : '') . $dir; print $cgi->a({-href => href(action=>"tree", file_name=>$fullname, hash_base=>$hb), - -title => $fullname}, esc_html($dir . '/')); - print " "; + -title => $fullname}, esc_html($dir)); + print " / "; } if (defined $type && $type eq 'blob') { print $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name, @@ -1536,7 +1536,7 @@ sub git_print_page_path { } elsif (defined $type && $type eq 'tree') { print $cgi->a({-href => href(action=>"tree", file_name=>$file_name, hash_base=>$hb), - -title => $name}, esc_html($basename . '/')); + -title => $name}, esc_html($basename)); } else { print esc_html($basename); } -- cgit v0.10.2-6-g49f6 From d72672074361a738afbf3139d8921ddf8bd782f8 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Fri, 22 Sep 2006 16:56:43 -0700 Subject: gitweb: Fix tree link associated with each commit log entry. The link forgot to have hb parameter and the resulting tree view failed to show the navbar for that commit. Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index b9df3cc..30d7d76 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2871,7 +2871,7 @@ sub git_log { " | " . $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " . - $cgi->a({-href => href(action=>"tree", hash=>$commit), hash_base=>$commit}, "tree") . + $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree") . "
\n" . "
\n" . "" . esc_html($co{'author_name'}) . " [$ad{'rfc2822'}]
\n" . -- cgit v0.10.2-6-g49f6 From 74d6166751ddcf08029ffc90a14158a86f80cd40 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sat, 23 Sep 2006 01:15:18 +0200 Subject: gitweb: Fix @git_base_url_list usage As it is now, that array was never used because the customurl accessor was broken and ''unless @url_list'' never happenned. Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 30d7d76..7ff5c04 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -752,7 +752,7 @@ sub git_get_project_description { sub git_get_project_url_list { my $path = shift; - open my $fd, "$projectroot/$path/cloneurl" or return undef; + open my $fd, "$projectroot/$path/cloneurl" or return; my @git_project_url_list = map { chomp; $_ } <$fd>; close $fd; -- cgit v0.10.2-6-g49f6 From 5c7d2cf3d6a059038d8d0bda6a76fa7818a9caa0 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sat, 23 Sep 2006 01:21:20 +0200 Subject: Fix snapshot link in tree view It would just give HEAD snapshot instead of one of the particular tree. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 7ff5c04..3d06181 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2762,7 +2762,7 @@ sub git_tree { if ($have_snapshot) { # FIXME: Should be available when we have no hash base as well. push @views_nav, - $cgi->a({-href => href(action=>"snapshot")}, + $cgi->a({-href => href(action=>"snapshot", hash=>$hash)}, "snapshot"); } git_print_page_nav('tree','', $hash_base, undef, undef, join(' | ', @views_nav)); -- cgit v0.10.2-6-g49f6 From 3d5c0cc9387b35df47c988fbc0e4379e413d783e Mon Sep 17 00:00:00 2001 From: Robin Rosenberg Date: Sat, 23 Sep 2006 00:35:20 +0200 Subject: Quote arguments to tr in test-lib When there are single-character filenames in the test directory, the shell tries to expand regexps meant for tr. Signed-off-by: Junio C Hamano diff --git a/t/test-lib.sh b/t/test-lib.sh index e262933..e75ad5f 100755 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -34,7 +34,7 @@ export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME export EDITOR VISUAL -case $(echo $GIT_TRACE |tr [A-Z] [a-z]) in +case $(echo $GIT_TRACE |tr "[A-Z]" "[a-z]") in 1|2|true) echo "* warning: Some tests will not work if GIT_TRACE" \ "is set as to trace on STDERR ! *" -- cgit v0.10.2-6-g49f6 From ae35b30433f5b732bd21f9577711584e3f9bba06 Mon Sep 17 00:00:00 2001 From: Sasha Khapyorsky Date: Tue, 5 Sep 2006 21:46:11 +0300 Subject: git-svnimport: Parse log message for Signed-off-by: lines This add '-S' option. When specified svn-import will try to parse commit message for 'Signed-off-by: ...' line, and if found will use the name and email address extracted at first occurrence as this commit author name and author email address. Committer name and email are extracted in usual way. Signed-off-by: Sasha Khapyorsky Signed-off-by: Junio C Hamano diff --git a/git-svnimport.perl b/git-svnimport.perl index 26dc454..ed62897 100755 --- a/git-svnimport.perl +++ b/git-svnimport.perl @@ -31,7 +31,7 @@ $SIG{'PIPE'}="IGNORE"; $ENV{'TZ'}="UTC"; our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T, - $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D); + $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S); sub usage() { print STDERR <$/) { - ($author_name, $author_email) = ($1, $2); + ($committer_name, $committer_email) = ($1, $2); } else { $author =~ s/^<(.*)>$/$1/; - $author_name = $author_email = $author; + $committer_name = $committer_email = $author; + } + + if ($opt_S && $message =~ /Signed-off-by:\s+(.*?)\s+<(.*)>\s*\n/) { + ($author_name, $author_email) = ($1, $2); + } else { + $author_name = $committer_name; + $author_email = $committer_email; } + $date = pdate($date); my $tag; @@ -772,8 +781,8 @@ sub commit { "GIT_AUTHOR_NAME=$author_name", "GIT_AUTHOR_EMAIL=$author_email", "GIT_AUTHOR_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)), - "GIT_COMMITTER_NAME=$author_name", - "GIT_COMMITTER_EMAIL=$author_email", + "GIT_COMMITTER_NAME=$committer_name", + "GIT_COMMITTER_EMAIL=$committer_email", "GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)), "git-commit-tree", $tree,@par); die "Cannot exec git-commit-tree: $!\n"; @@ -825,7 +834,7 @@ sub commit { print $out ("object $cid\n". "type commit\n". "tag $dest\n". - "tagger $author_name <$author_email>\n") and + "tagger $committer_name <$committer_email>\n") and close($out) or die "Cannot create tag object $dest: $!\n"; -- cgit v0.10.2-6-g49f6 From 16854571aae6302f457c5fbee41ac64669b09595 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Thu, 21 Sep 2006 00:11:59 -0400 Subject: move pack creation to version 3 It's been quite a while now that GIT is able to read version 3 packs. Let's create them at last. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/diff-delta.c b/diff-delta.c index fa16d06..51df460 100644 --- a/diff-delta.c +++ b/diff-delta.c @@ -308,8 +308,8 @@ create_delta(const struct delta_index *index, continue; if (ref_size > top - src) ref_size = top - src; - if (ref_size > 0x10000) - ref_size = 0x10000; + if (ref_size > 0xffffff) + ref_size = 0xffffff; if (ref_size <= msize) break; while (ref_size-- && *src++ == *ref) @@ -318,6 +318,8 @@ create_delta(const struct delta_index *index, /* this is our best match so far */ msize = ref - entry->ptr; moff = entry->ptr - ref_data; + if (msize >= 0x10000) + break; /* this is good enough */ } } @@ -381,6 +383,8 @@ create_delta(const struct delta_index *index, if (msize & 0xff) { out[outpos++] = msize; i |= 0x10; } msize >>= 8; if (msize & 0xff) { out[outpos++] = msize; i |= 0x20; } + msize >>= 8; + if (msize & 0xff) { out[outpos++] = msize; i |= 0x40; } *op = i; } diff --git a/pack.h b/pack.h index eb07b03..05557da 100644 --- a/pack.h +++ b/pack.h @@ -7,7 +7,7 @@ * Packed object header */ #define PACK_SIGNATURE 0x5041434b /* "PACK" */ -#define PACK_VERSION 2 +#define PACK_VERSION 3 #define pack_version_ok(v) ((v) == htonl(2) || (v) == htonl(3)) struct pack_header { unsigned int hdr_signature; -- cgit v0.10.2-6-g49f6 From 43057304c0bbaf7bc7511daaf81df08c7909a90b Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Thu, 21 Sep 2006 00:05:37 -0400 Subject: many cleanups to sha1_file.c Those cleanups are mainly to set the table for the support of deltas with base objects referenced by offsets instead of sha1. This means that many pack lookup functions are converted to take a pack/offset tuple instead of a sha1. This eliminates many struct pack_entry usages since this structure carried redundent information in many cases, and it increased stack footprint needlessly for a couple recursively called functions that used to declare a local copy of it for every recursion loop. In the process, packed_object_info_detail() has been reorganized as well so to look much saner and more amenable to deltas with offset support. Finally the appropriate adjustments have been made to functions that depend on the above changes. But there is no functionality changes yet simply some code refactoring at this point. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 8d7a120..96c069a 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -597,15 +597,15 @@ static int add_object_entry(const unsigned char *sha1, unsigned hash, int exclud if (!exclude) { for (p = packed_git; p; p = p->next) { - struct pack_entry e; - if (find_pack_entry_one(sha1, &e, p)) { + unsigned long offset = find_pack_entry_one(sha1, p); + if (offset) { if (incremental) return 0; if (local && !p->pack_local) return 0; if (!found_pack) { - found_offset = e.offset; - found_pack = e.p; + found_offset = offset; + found_pack = p; } } } diff --git a/cache.h b/cache.h index ef2e581..97debd0 100644 --- a/cache.h +++ b/cache.h @@ -390,10 +390,10 @@ extern void unuse_packed_git(struct packed_git *); extern struct packed_git *add_packed_git(char *, int, int); extern int num_packed_objects(const struct packed_git *p); extern int nth_packed_object_sha1(const struct packed_git *, int, unsigned char*); -extern int find_pack_entry_one(const unsigned char *, struct pack_entry *, struct packed_git *); -extern void *unpack_entry_gently(struct pack_entry *, char *, unsigned long *); +extern unsigned long find_pack_entry_one(const unsigned char *, struct packed_git *); +extern void *unpack_entry_gently(struct packed_git *, unsigned long, char *, unsigned long *); extern unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep); -extern void packed_object_info_detail(struct pack_entry *, char *, unsigned long *, unsigned long *, unsigned int *, unsigned char *); +extern void packed_object_info_detail(struct packed_git *, unsigned long, char *, unsigned long *, unsigned long *, unsigned int *, unsigned char *); /* Dumb servers support */ extern int update_server_info(int); diff --git a/pack-check.c b/pack-check.c index 04c6c00..c0caaee 100644 --- a/pack-check.c +++ b/pack-check.c @@ -42,16 +42,16 @@ static int verify_packfile(struct packed_git *p) */ for (i = err = 0; i < nr_objects; i++) { unsigned char sha1[20]; - struct pack_entry e; void *data; char type[20]; - unsigned long size; + unsigned long size, offset; if (nth_packed_object_sha1(p, i, sha1)) die("internal error pack-check nth-packed-object"); - if (!find_pack_entry_one(sha1, &e, p)) + offset = find_pack_entry_one(sha1, p); + if (!offset) die("internal error pack-check find-pack-entry-one"); - data = unpack_entry_gently(&e, type, &size); + data = unpack_entry_gently(p, offset, type, &size); if (!data) { err = error("cannot unpack %s from %s", sha1_to_hex(sha1), p->pack_name); @@ -84,25 +84,26 @@ static void show_pack_info(struct packed_git *p) for (i = 0; i < nr_objects; i++) { unsigned char sha1[20], base_sha1[20]; - struct pack_entry e; char type[20]; unsigned long size; unsigned long store_size; + unsigned long offset; unsigned int delta_chain_length; if (nth_packed_object_sha1(p, i, sha1)) die("internal error pack-check nth-packed-object"); - if (!find_pack_entry_one(sha1, &e, p)) + offset = find_pack_entry_one(sha1, p); + if (!offset) die("internal error pack-check find-pack-entry-one"); - packed_object_info_detail(&e, type, &size, &store_size, + packed_object_info_detail(p, offset, type, &size, &store_size, &delta_chain_length, base_sha1); printf("%s ", sha1_to_hex(sha1)); if (!delta_chain_length) - printf("%-6s %lu %u\n", type, size, e.offset); + printf("%-6s %lu %lu\n", type, size, offset); else { - printf("%-6s %lu %u %u %s\n", type, size, e.offset, + printf("%-6s %lu %lu %u %s\n", type, size, offset, delta_chain_length, sha1_to_hex(base_sha1)); if (delta_chain_length < MAX_CHAIN) chain_histogram[delta_chain_length]++; diff --git a/sha1_file.c b/sha1_file.c index 0f9c2b6..27b1ebb 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -884,33 +884,32 @@ void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned l } /* forward declaration for a mutually recursive function */ -static int packed_object_info(struct pack_entry *entry, +static int packed_object_info(struct packed_git *p, unsigned long offset, char *type, unsigned long *sizep); -static int packed_delta_info(unsigned char *base_sha1, - unsigned long delta_size, - unsigned long left, +static int packed_delta_info(struct packed_git *p, + unsigned long offset, char *type, - unsigned long *sizep, - struct packed_git *p) + unsigned long *sizep) { - struct pack_entry base_ent; + unsigned long base_offset; + unsigned char *base_sha1 = (unsigned char *) p->pack_base + offset; - if (left < 20) + if (p->pack_size < offset + 20) die("truncated pack file"); - /* The base entry _must_ be in the same pack */ - if (!find_pack_entry_one(base_sha1, &base_ent, p)) + base_offset = find_pack_entry_one(base_sha1, p); + if (!base_offset) die("failed to find delta-pack base object %s", sha1_to_hex(base_sha1)); + offset += 20; /* We choose to only get the type of the base object and * ignore potentially corrupt pack file that expects the delta * based on a base with a wrong size. This saves tons of * inflate() calls. */ - - if (packed_object_info(&base_ent, type, NULL)) + if (packed_object_info(p, base_offset, type, NULL)) die("cannot get info for delta-pack base"); if (sizep) { @@ -922,8 +921,8 @@ static int packed_delta_info(unsigned char *base_sha1, memset(&stream, 0, sizeof(stream)); - data = stream.next_in = base_sha1 + 20; - stream.avail_in = left - 20; + stream.next_in = (unsigned char *) p->pack_base + offset; + stream.avail_in = p->pack_size - offset; stream.next_out = delta_head; stream.avail_out = sizeof(delta_head); @@ -985,75 +984,60 @@ int check_reuse_pack_delta(struct packed_git *p, unsigned long offset, return status; } -void packed_object_info_detail(struct pack_entry *e, +void packed_object_info_detail(struct packed_git *p, + unsigned long offset, char *type, unsigned long *size, unsigned long *store_size, unsigned int *delta_chain_length, unsigned char *base_sha1) { - struct packed_git *p = e->p; - unsigned long offset; - unsigned char *pack; + unsigned long val; + unsigned char *next_sha1; enum object_type kind; - offset = unpack_object_header(p, e->offset, &kind, size); - pack = (unsigned char *) p->pack_base + offset; - if (kind != OBJ_DELTA) - *delta_chain_length = 0; - else { - unsigned int chain_length = 0; - if (p->pack_size <= offset + 20) - die("pack file %s records an incomplete delta base", - p->pack_name); - hashcpy(base_sha1, pack); - do { - struct pack_entry base_ent; - unsigned long junk; - - find_pack_entry_one(pack, &base_ent, p); - offset = unpack_object_header(p, base_ent.offset, - &kind, &junk); - pack = (unsigned char *) p->pack_base + offset; - chain_length++; - } while (kind == OBJ_DELTA); - *delta_chain_length = chain_length; - } - switch (kind) { - case OBJ_COMMIT: - case OBJ_TREE: - case OBJ_BLOB: - case OBJ_TAG: - strcpy(type, type_names[kind]); - break; - default: - die("corrupted pack file %s containing object of kind %d", - p->pack_name, kind); + *delta_chain_length = 0; + offset = unpack_object_header(p, offset, &kind, size); + + for (;;) { + switch (kind) { + default: + die("corrupted pack file %s containing object of kind %d", + p->pack_name, kind); + case OBJ_COMMIT: + case OBJ_TREE: + case OBJ_BLOB: + case OBJ_TAG: + strcpy(type, type_names[kind]); + *store_size = 0; /* notyet */ + return; + case OBJ_DELTA: + if (p->pack_size <= offset + 20) + die("pack file %s records an incomplete delta base", + p->pack_name); + next_sha1 = (unsigned char *) p->pack_base + offset; + if (*delta_chain_length == 0) + hashcpy(base_sha1, next_sha1); + offset = find_pack_entry_one(next_sha1, p); + break; + } + offset = unpack_object_header(p, offset, &kind, &val); + (*delta_chain_length)++; } - *store_size = 0; /* notyet */ } -static int packed_object_info(struct pack_entry *entry, +static int packed_object_info(struct packed_git *p, unsigned long offset, char *type, unsigned long *sizep) { - struct packed_git *p = entry->p; - unsigned long offset, size, left; - unsigned char *pack; + unsigned long size; enum object_type kind; - int retval; - if (use_packed_git(p)) - die("cannot map packed file"); + offset = unpack_object_header(p, offset, &kind, &size); - offset = unpack_object_header(p, entry->offset, &kind, &size); - pack = (unsigned char *) p->pack_base + offset; - left = p->pack_size - offset; + if (kind == OBJ_DELTA) + return packed_delta_info(p, offset, type, sizep); switch (kind) { - case OBJ_DELTA: - retval = packed_delta_info(pack, size, left, type, sizep, p); - unuse_packed_git(p); - return retval; case OBJ_COMMIT: case OBJ_TREE: case OBJ_BLOB: @@ -1066,7 +1050,6 @@ static int packed_object_info(struct pack_entry *entry, } if (sizep) *sizep = size; - unuse_packed_git(p); return 0; } @@ -1103,25 +1086,26 @@ static void *unpack_delta_entry(struct packed_git *p, char *type, unsigned long *sizep) { - struct pack_entry base_ent; void *delta_data, *result, *base; - unsigned long result_size, base_size; - unsigned char* base_sha1; + unsigned long result_size, base_size, base_offset; + unsigned char *base_sha1; - if ((offset + 20) >= p->pack_size) + if (p->pack_size < offset + 20) die("truncated pack file"); - /* The base entry _must_ be in the same pack */ base_sha1 = (unsigned char*)p->pack_base + offset; - if (!find_pack_entry_one(base_sha1, &base_ent, p)) + base_offset = find_pack_entry_one(base_sha1, p); + if (!base_offset) die("failed to find delta-pack base object %s", sha1_to_hex(base_sha1)); - base = unpack_entry_gently(&base_ent, type, &base_size); + offset += 20; + + base = unpack_entry_gently(p, base_offset, type, &base_size); if (!base) - die("failed to read delta-pack base object %s", - sha1_to_hex(base_sha1)); + die("failed to read delta base object at %lu from %s", + base_offset, p->pack_name); - delta_data = unpack_compressed_entry(p, offset + 20, delta_size); + delta_data = unpack_compressed_entry(p, offset, delta_size); result = patch_delta(base, base_size, delta_data, delta_size, &result_size); @@ -1141,7 +1125,7 @@ static void *unpack_entry(struct pack_entry *entry, if (use_packed_git(p)) die("cannot map packed file"); - retval = unpack_entry_gently(entry, type, sizep); + retval = unpack_entry_gently(p, entry->offset, type, sizep); unuse_packed_git(p); if (!retval) die("corrupted pack file %s", p->pack_name); @@ -1149,14 +1133,13 @@ static void *unpack_entry(struct pack_entry *entry, } /* The caller is responsible for use_packed_git()/unuse_packed_git() pair */ -void *unpack_entry_gently(struct pack_entry *entry, +void *unpack_entry_gently(struct packed_git *p, unsigned long offset, char *type, unsigned long *sizep) { - struct packed_git *p = entry->p; - unsigned long offset, size; + unsigned long size; enum object_type kind; - offset = unpack_object_header(p, entry->offset, &kind, &size); + offset = unpack_object_header(p, offset, &kind, &size); switch (kind) { case OBJ_DELTA: return unpack_delta_entry(p, offset, size, type, sizep); @@ -1188,8 +1171,8 @@ int nth_packed_object_sha1(const struct packed_git *p, int n, return 0; } -int find_pack_entry_one(const unsigned char *sha1, - struct pack_entry *e, struct packed_git *p) +unsigned long find_pack_entry_one(const unsigned char *sha1, + struct packed_git *p) { unsigned int *level1_ofs = p->index_base; int hi = ntohl(level1_ofs[*sha1]); @@ -1199,12 +1182,8 @@ int find_pack_entry_one(const unsigned char *sha1, do { int mi = (lo + hi) / 2; int cmp = hashcmp((unsigned char *)index + (24 * mi) + 4, sha1); - if (!cmp) { - e->offset = ntohl(*((unsigned int *) ((char *) index + (24 * mi)))); - hashcpy(e->sha1, sha1); - e->p = p; - return 1; - } + if (!cmp) + return ntohl(*((unsigned int *) ((char *) index + (24 * mi)))); if (cmp > 0) hi = mi; else @@ -1216,6 +1195,8 @@ int find_pack_entry_one(const unsigned char *sha1, static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, const char **ignore_packed) { struct packed_git *p; + unsigned long offset; + prepare_packed_git(); for (p = packed_git; p; p = p->next) { @@ -1227,8 +1208,13 @@ static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, cons if (*ig) continue; } - if (find_pack_entry_one(sha1, e, p)) + offset = find_pack_entry_one(sha1, p); + if (offset) { + e->offset = offset; + e->p = p; + hashcpy(e->sha1, sha1); return 1; + } } return 0; } @@ -1237,10 +1223,9 @@ struct packed_git *find_sha1_pack(const unsigned char *sha1, struct packed_git *packs) { struct packed_git *p; - struct pack_entry e; for (p = packs; p; p = p->next) { - if (find_pack_entry_one(sha1, &e, p)) + if (find_pack_entry_one(sha1, p)) return p; } return NULL; @@ -1259,12 +1244,16 @@ int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep if (!map) { struct pack_entry e; - if (find_pack_entry(sha1, &e, NULL)) - return packed_object_info(&e, type, sizep); - reprepare_packed_git(); - if (find_pack_entry(sha1, &e, NULL)) - return packed_object_info(&e, type, sizep); - return error("unable to find %s", sha1_to_hex(sha1)); + if (!find_pack_entry(sha1, &e, NULL)) { + reprepare_packed_git(); + if (!find_pack_entry(sha1, &e, NULL)) + return error("unable to find %s", sha1_to_hex(sha1)); + } + if (use_packed_git(e.p)) + die("cannot map packed file"); + status = packed_object_info(e.p, e.offset, type, sizep); + unuse_packed_git(e.p); + return status; } if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0) status = error("unable to unpack %s header", -- cgit v0.10.2-6-g49f6 From 31756c5da8198eb09f2792230e1bed4d107f5135 Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Sat, 23 Sep 2006 17:06:11 +0200 Subject: Remove git-zip-tree git-zip-tree can be safely removed because it was never part of a formal release. This patch makes 'git-archive --format=zip' the one and only git ZIP file creation command. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano diff --git a/.gitignore b/.gitignore index a3d9c7a..3ca66e4 100644 --- a/.gitignore +++ b/.gitignore @@ -128,7 +128,6 @@ git-verify-pack git-verify-tag git-whatchanged git-write-tree -git-zip-tree git-core-*/?* gitweb/gitweb.cgi test-date diff --git a/Documentation/git-zip-tree.txt b/Documentation/git-zip-tree.txt deleted file mode 100644 index 2e9d981..0000000 --- a/Documentation/git-zip-tree.txt +++ /dev/null @@ -1,67 +0,0 @@ -git-zip-tree(1) -=============== - -NAME ----- -git-zip-tree - Creates a ZIP archive of the files in the named tree - - -SYNOPSIS --------- -'git-zip-tree' [-0|...|-9] [ ] - -DESCRIPTION ------------ -Creates a ZIP archive containing the tree structure for the named tree. -When is specified it is added as a leading path to the files in the -generated ZIP archive. - -git-zip-tree behaves differently when given a tree ID versus when given -a commit ID or tag ID. In the first case the current time is used as -modification time of each file in the archive. In the latter case the -commit time as recorded in the referenced commit object is used instead. -Additionally the commit ID is stored as an archive comment. - -Currently git-zip-tree can handle only files and directories, symbolic -links are not supported. - -OPTIONS -------- - --0:: - Store the files instead of deflating them. - --9:: - Highest and slowest compression level. You can specify any - number from 1 to 9 to adjust compression speed and ratio. - -:: - The tree or commit to produce ZIP archive for. If it is - the object name of a commit object. - -:: - Leading path to the files in the resulting ZIP archive. - -EXAMPLES --------- -git zip-tree v1.4.0 git-1.4.0 >git-1.4.0.zip:: - - Create a ZIP file for v1.4.0 release. - -git zip-tree HEAD:Documentation/ git-docs >docs.zip:: - - Put everything in the current head's Documentation/ directory - into 'docs.zip', with the prefix 'git-docs/'. - -Author ------- -Written by Rene Scharfe. - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list . - -GIT ---- -Part of the gitlink:git[7] suite - diff --git a/builtin-zip-tree.c b/builtin-zip-tree.c index 52d4b7a..3ffdad6 100644 --- a/builtin-zip-tree.c +++ b/builtin-zip-tree.c @@ -10,9 +10,6 @@ #include "builtin.h" #include "archive.h" -static const char zip_tree_usage[] = -"git-zip-tree [-0|...|-9] [ ]"; - static int verbose; static int zip_date; static int zip_time; @@ -294,68 +291,6 @@ static void dos_time(time_t *time, int *dos_date, int *dos_time) *dos_time = t->tm_sec / 2 + t->tm_min * 32 + t->tm_hour * 2048; } -int cmd_zip_tree(int argc, const char **argv, const char *prefix) -{ - unsigned char sha1[20]; - struct tree *tree; - struct commit *commit; - time_t archive_time; - char *base; - int baselen; - - git_config(git_default_config); - - if (argc > 1 && argv[1][0] == '-') { - if (isdigit(argv[1][1]) && argv[1][2] == '\0') { - zlib_compression_level = argv[1][1] - '0'; - argc--; - argv++; - } - } - - switch (argc) { - case 3: - base = xstrdup(argv[2]); - baselen = strlen(base); - break; - case 2: - base = xstrdup(""); - baselen = 0; - break; - default: - usage(zip_tree_usage); - } - - if (get_sha1(argv[1], sha1)) - die("Not a valid object name %s", argv[1]); - - commit = lookup_commit_reference_gently(sha1, 1); - archive_time = commit ? commit->date : time(NULL); - dos_time(&archive_time, &zip_date, &zip_time); - - zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE); - zip_dir_size = ZIP_DIRECTORY_MIN_SIZE; - - tree = parse_tree_indirect(sha1); - if (!tree) - die("not a tree object"); - - if (baselen > 0) { - write_zip_entry(tree->object.sha1, "", 0, base, 040777, 0); - base = xrealloc(base, baselen + 1); - base[baselen] = '/'; - baselen++; - base[baselen] = '\0'; - } - read_tree_recursive(tree, base, baselen, 0, NULL, write_zip_entry); - write_zip_trailer(commit ? commit->object.sha1 : NULL); - - free(zip_dir); - free(base); - - return 0; -} - int write_zip_archive(struct archiver_args *args) { int plen = strlen(args->base); diff --git a/builtin.h b/builtin.h index ccade94..f9fa9ff 100644 --- a/builtin.h +++ b/builtin.h @@ -53,7 +53,6 @@ extern int cmd_show(int argc, const char **argv, const char *prefix); extern int cmd_stripspace(int argc, const char **argv, const char *prefix); extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix); extern int cmd_tar_tree(int argc, const char **argv, const char *prefix); -extern int cmd_zip_tree(int argc, const char **argv, const char *prefix); extern int cmd_unpack_objects(int argc, const char **argv, const char *prefix); extern int cmd_update_index(int argc, const char **argv, const char *prefix); extern int cmd_update_ref(int argc, const char **argv, const char *prefix); diff --git a/git.c b/git.c index 44ab0de..1686220 100644 --- a/git.c +++ b/git.c @@ -259,7 +259,6 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "stripspace", cmd_stripspace }, { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, { "tar-tree", cmd_tar_tree, RUN_SETUP }, - { "zip-tree", cmd_zip_tree, RUN_SETUP }, { "unpack-objects", cmd_unpack_objects, RUN_SETUP }, { "update-index", cmd_update_index, RUN_SETUP }, { "update-ref", cmd_update_ref, RUN_SETUP }, -- cgit v0.10.2-6-g49f6 From 3fc8284e2114624f2657142b3fecdc6f514b2090 Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Sat, 23 Sep 2006 17:06:35 +0200 Subject: Rename builtin-zip-tree.c to archive-zip.c Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 8467447..739d7e3 100644 --- a/Makefile +++ b/Makefile @@ -254,7 +254,7 @@ LIB_OBJS = \ fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \ write_or_die.o trace.o list-objects.o \ alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \ - color.o wt-status.o + color.o wt-status.o archive-zip.o BUILTIN_OBJS = \ builtin-add.o \ @@ -300,8 +300,7 @@ BUILTIN_OBJS = \ builtin-upload-archive.o \ builtin-upload-tar.o \ builtin-verify-pack.o \ - builtin-write-tree.o \ - builtin-zip-tree.o + builtin-write-tree.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz diff --git a/archive-zip.c b/archive-zip.c new file mode 100644 index 0000000..3ffdad6 --- /dev/null +++ b/archive-zip.c @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2006 Rene Scharfe + */ +#include +#include "cache.h" +#include "commit.h" +#include "blob.h" +#include "tree.h" +#include "quote.h" +#include "builtin.h" +#include "archive.h" + +static int verbose; +static int zip_date; +static int zip_time; + +static unsigned char *zip_dir; +static unsigned int zip_dir_size; + +static unsigned int zip_offset; +static unsigned int zip_dir_offset; +static unsigned int zip_dir_entries; + +#define ZIP_DIRECTORY_MIN_SIZE (1024 * 1024) + +struct zip_local_header { + unsigned char magic[4]; + unsigned char version[2]; + unsigned char flags[2]; + unsigned char compression_method[2]; + unsigned char mtime[2]; + unsigned char mdate[2]; + unsigned char crc32[4]; + unsigned char compressed_size[4]; + unsigned char size[4]; + unsigned char filename_length[2]; + unsigned char extra_length[2]; +}; + +struct zip_dir_header { + unsigned char magic[4]; + unsigned char creator_version[2]; + unsigned char version[2]; + unsigned char flags[2]; + unsigned char compression_method[2]; + unsigned char mtime[2]; + unsigned char mdate[2]; + unsigned char crc32[4]; + unsigned char compressed_size[4]; + unsigned char size[4]; + unsigned char filename_length[2]; + unsigned char extra_length[2]; + unsigned char comment_length[2]; + unsigned char disk[2]; + unsigned char attr1[2]; + unsigned char attr2[4]; + unsigned char offset[4]; +}; + +struct zip_dir_trailer { + unsigned char magic[4]; + unsigned char disk[2]; + unsigned char directory_start_disk[2]; + unsigned char entries_on_this_disk[2]; + unsigned char entries[2]; + unsigned char size[4]; + unsigned char offset[4]; + unsigned char comment_length[2]; +}; + +static void copy_le16(unsigned char *dest, unsigned int n) +{ + dest[0] = 0xff & n; + dest[1] = 0xff & (n >> 010); +} + +static void copy_le32(unsigned char *dest, unsigned int n) +{ + dest[0] = 0xff & n; + dest[1] = 0xff & (n >> 010); + dest[2] = 0xff & (n >> 020); + dest[3] = 0xff & (n >> 030); +} + +static void *zlib_deflate(void *data, unsigned long size, + unsigned long *compressed_size) +{ + z_stream stream; + unsigned long maxsize; + void *buffer; + int result; + + memset(&stream, 0, sizeof(stream)); + deflateInit(&stream, zlib_compression_level); + maxsize = deflateBound(&stream, size); + buffer = xmalloc(maxsize); + + stream.next_in = data; + stream.avail_in = size; + stream.next_out = buffer; + stream.avail_out = maxsize; + + do { + result = deflate(&stream, Z_FINISH); + } while (result == Z_OK); + + if (result != Z_STREAM_END) { + free(buffer); + return NULL; + } + + deflateEnd(&stream); + *compressed_size = stream.total_out; + + return buffer; +} + +static char *construct_path(const char *base, int baselen, + const char *filename, int isdir, int *pathlen) +{ + int filenamelen = strlen(filename); + int len = baselen + filenamelen; + char *path, *p; + + if (isdir) + len++; + p = path = xmalloc(len + 1); + + memcpy(p, base, baselen); + p += baselen; + memcpy(p, filename, filenamelen); + p += filenamelen; + if (isdir) + *p++ = '/'; + *p = '\0'; + + *pathlen = len; + + return path; +} + +static int write_zip_entry(const unsigned char *sha1, + const char *base, int baselen, + const char *filename, unsigned mode, int stage) +{ + struct zip_local_header header; + struct zip_dir_header dirent; + unsigned long compressed_size; + unsigned long uncompressed_size; + unsigned long crc; + unsigned long direntsize; + unsigned long size; + int method; + int result = -1; + int pathlen; + unsigned char *out; + char *path; + char type[20]; + void *buffer = NULL; + void *deflated = NULL; + + crc = crc32(0, Z_NULL, 0); + + path = construct_path(base, baselen, filename, S_ISDIR(mode), &pathlen); + if (verbose) + fprintf(stderr, "%s\n", path); + if (pathlen > 0xffff) { + error("path too long (%d chars, SHA1: %s): %s", pathlen, + sha1_to_hex(sha1), path); + goto out; + } + + if (S_ISDIR(mode)) { + method = 0; + result = READ_TREE_RECURSIVE; + out = NULL; + uncompressed_size = 0; + compressed_size = 0; + } else if (S_ISREG(mode)) { + method = zlib_compression_level == 0 ? 0 : 8; + result = 0; + buffer = read_sha1_file(sha1, type, &size); + if (!buffer) + die("cannot read %s", sha1_to_hex(sha1)); + crc = crc32(crc, buffer, size); + out = buffer; + uncompressed_size = size; + compressed_size = size; + } else { + error("unsupported file mode: 0%o (SHA1: %s)", mode, + sha1_to_hex(sha1)); + goto out; + } + + if (method == 8) { + deflated = zlib_deflate(buffer, size, &compressed_size); + if (deflated && compressed_size - 6 < size) { + /* ZLIB --> raw compressed data (see RFC 1950) */ + /* CMF and FLG ... */ + out = (unsigned char *)deflated + 2; + compressed_size -= 6; /* ... and ADLER32 */ + } else { + method = 0; + compressed_size = size; + } + } + + /* make sure we have enough free space in the dictionary */ + direntsize = sizeof(struct zip_dir_header) + pathlen; + while (zip_dir_size < zip_dir_offset + direntsize) { + zip_dir_size += ZIP_DIRECTORY_MIN_SIZE; + zip_dir = xrealloc(zip_dir, zip_dir_size); + } + + copy_le32(dirent.magic, 0x02014b50); + copy_le16(dirent.creator_version, 0); + copy_le16(dirent.version, 20); + copy_le16(dirent.flags, 0); + copy_le16(dirent.compression_method, method); + copy_le16(dirent.mtime, zip_time); + copy_le16(dirent.mdate, zip_date); + copy_le32(dirent.crc32, crc); + copy_le32(dirent.compressed_size, compressed_size); + copy_le32(dirent.size, uncompressed_size); + copy_le16(dirent.filename_length, pathlen); + copy_le16(dirent.extra_length, 0); + copy_le16(dirent.comment_length, 0); + copy_le16(dirent.disk, 0); + copy_le16(dirent.attr1, 0); + copy_le32(dirent.attr2, 0); + copy_le32(dirent.offset, zip_offset); + memcpy(zip_dir + zip_dir_offset, &dirent, sizeof(struct zip_dir_header)); + zip_dir_offset += sizeof(struct zip_dir_header); + memcpy(zip_dir + zip_dir_offset, path, pathlen); + zip_dir_offset += pathlen; + zip_dir_entries++; + + copy_le32(header.magic, 0x04034b50); + copy_le16(header.version, 20); + copy_le16(header.flags, 0); + copy_le16(header.compression_method, method); + copy_le16(header.mtime, zip_time); + copy_le16(header.mdate, zip_date); + copy_le32(header.crc32, crc); + copy_le32(header.compressed_size, compressed_size); + copy_le32(header.size, uncompressed_size); + copy_le16(header.filename_length, pathlen); + copy_le16(header.extra_length, 0); + write_or_die(1, &header, sizeof(struct zip_local_header)); + zip_offset += sizeof(struct zip_local_header); + write_or_die(1, path, pathlen); + zip_offset += pathlen; + if (compressed_size > 0) { + write_or_die(1, out, compressed_size); + zip_offset += compressed_size; + } + +out: + free(buffer); + free(deflated); + free(path); + + return result; +} + +static void write_zip_trailer(const unsigned char *sha1) +{ + struct zip_dir_trailer trailer; + + copy_le32(trailer.magic, 0x06054b50); + copy_le16(trailer.disk, 0); + copy_le16(trailer.directory_start_disk, 0); + copy_le16(trailer.entries_on_this_disk, zip_dir_entries); + copy_le16(trailer.entries, zip_dir_entries); + copy_le32(trailer.size, zip_dir_offset); + copy_le32(trailer.offset, zip_offset); + copy_le16(trailer.comment_length, sha1 ? 40 : 0); + + write_or_die(1, zip_dir, zip_dir_offset); + write_or_die(1, &trailer, sizeof(struct zip_dir_trailer)); + if (sha1) + write_or_die(1, sha1_to_hex(sha1), 40); +} + +static void dos_time(time_t *time, int *dos_date, int *dos_time) +{ + struct tm *t = localtime(time); + + *dos_date = t->tm_mday + (t->tm_mon + 1) * 32 + + (t->tm_year + 1900 - 1980) * 512; + *dos_time = t->tm_sec / 2 + t->tm_min * 32 + t->tm_hour * 2048; +} + +int write_zip_archive(struct archiver_args *args) +{ + int plen = strlen(args->base); + + dos_time(&args->time, &zip_date, &zip_time); + + zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE); + zip_dir_size = ZIP_DIRECTORY_MIN_SIZE; + verbose = args->verbose; + + if (args->base && plen > 0 && args->base[plen - 1] == '/') { + char *base = xstrdup(args->base); + int baselen = strlen(base); + + while (baselen > 0 && base[baselen - 1] == '/') + base[--baselen] = '\0'; + write_zip_entry(args->tree->object.sha1, "", 0, base, 040777, 0); + free(base); + } + read_tree_recursive(args->tree, args->base, plen, 0, + args->pathspec, write_zip_entry); + write_zip_trailer(args->commit_sha1); + + free(zip_dir); + + return 0; +} + +void *parse_extra_zip_args(int argc, const char **argv) +{ + for (; argc > 0; argc--, argv++) { + const char *arg = argv[0]; + + if (arg[0] == '-' && isdigit(arg[1]) && arg[2] == '\0') + zlib_compression_level = arg[1] - '0'; + else + die("Unknown argument for zip format: %s", arg); + } + return NULL; +} diff --git a/builtin-zip-tree.c b/builtin-zip-tree.c deleted file mode 100644 index 3ffdad6..0000000 --- a/builtin-zip-tree.c +++ /dev/null @@ -1,333 +0,0 @@ -/* - * Copyright (c) 2006 Rene Scharfe - */ -#include -#include "cache.h" -#include "commit.h" -#include "blob.h" -#include "tree.h" -#include "quote.h" -#include "builtin.h" -#include "archive.h" - -static int verbose; -static int zip_date; -static int zip_time; - -static unsigned char *zip_dir; -static unsigned int zip_dir_size; - -static unsigned int zip_offset; -static unsigned int zip_dir_offset; -static unsigned int zip_dir_entries; - -#define ZIP_DIRECTORY_MIN_SIZE (1024 * 1024) - -struct zip_local_header { - unsigned char magic[4]; - unsigned char version[2]; - unsigned char flags[2]; - unsigned char compression_method[2]; - unsigned char mtime[2]; - unsigned char mdate[2]; - unsigned char crc32[4]; - unsigned char compressed_size[4]; - unsigned char size[4]; - unsigned char filename_length[2]; - unsigned char extra_length[2]; -}; - -struct zip_dir_header { - unsigned char magic[4]; - unsigned char creator_version[2]; - unsigned char version[2]; - unsigned char flags[2]; - unsigned char compression_method[2]; - unsigned char mtime[2]; - unsigned char mdate[2]; - unsigned char crc32[4]; - unsigned char compressed_size[4]; - unsigned char size[4]; - unsigned char filename_length[2]; - unsigned char extra_length[2]; - unsigned char comment_length[2]; - unsigned char disk[2]; - unsigned char attr1[2]; - unsigned char attr2[4]; - unsigned char offset[4]; -}; - -struct zip_dir_trailer { - unsigned char magic[4]; - unsigned char disk[2]; - unsigned char directory_start_disk[2]; - unsigned char entries_on_this_disk[2]; - unsigned char entries[2]; - unsigned char size[4]; - unsigned char offset[4]; - unsigned char comment_length[2]; -}; - -static void copy_le16(unsigned char *dest, unsigned int n) -{ - dest[0] = 0xff & n; - dest[1] = 0xff & (n >> 010); -} - -static void copy_le32(unsigned char *dest, unsigned int n) -{ - dest[0] = 0xff & n; - dest[1] = 0xff & (n >> 010); - dest[2] = 0xff & (n >> 020); - dest[3] = 0xff & (n >> 030); -} - -static void *zlib_deflate(void *data, unsigned long size, - unsigned long *compressed_size) -{ - z_stream stream; - unsigned long maxsize; - void *buffer; - int result; - - memset(&stream, 0, sizeof(stream)); - deflateInit(&stream, zlib_compression_level); - maxsize = deflateBound(&stream, size); - buffer = xmalloc(maxsize); - - stream.next_in = data; - stream.avail_in = size; - stream.next_out = buffer; - stream.avail_out = maxsize; - - do { - result = deflate(&stream, Z_FINISH); - } while (result == Z_OK); - - if (result != Z_STREAM_END) { - free(buffer); - return NULL; - } - - deflateEnd(&stream); - *compressed_size = stream.total_out; - - return buffer; -} - -static char *construct_path(const char *base, int baselen, - const char *filename, int isdir, int *pathlen) -{ - int filenamelen = strlen(filename); - int len = baselen + filenamelen; - char *path, *p; - - if (isdir) - len++; - p = path = xmalloc(len + 1); - - memcpy(p, base, baselen); - p += baselen; - memcpy(p, filename, filenamelen); - p += filenamelen; - if (isdir) - *p++ = '/'; - *p = '\0'; - - *pathlen = len; - - return path; -} - -static int write_zip_entry(const unsigned char *sha1, - const char *base, int baselen, - const char *filename, unsigned mode, int stage) -{ - struct zip_local_header header; - struct zip_dir_header dirent; - unsigned long compressed_size; - unsigned long uncompressed_size; - unsigned long crc; - unsigned long direntsize; - unsigned long size; - int method; - int result = -1; - int pathlen; - unsigned char *out; - char *path; - char type[20]; - void *buffer = NULL; - void *deflated = NULL; - - crc = crc32(0, Z_NULL, 0); - - path = construct_path(base, baselen, filename, S_ISDIR(mode), &pathlen); - if (verbose) - fprintf(stderr, "%s\n", path); - if (pathlen > 0xffff) { - error("path too long (%d chars, SHA1: %s): %s", pathlen, - sha1_to_hex(sha1), path); - goto out; - } - - if (S_ISDIR(mode)) { - method = 0; - result = READ_TREE_RECURSIVE; - out = NULL; - uncompressed_size = 0; - compressed_size = 0; - } else if (S_ISREG(mode)) { - method = zlib_compression_level == 0 ? 0 : 8; - result = 0; - buffer = read_sha1_file(sha1, type, &size); - if (!buffer) - die("cannot read %s", sha1_to_hex(sha1)); - crc = crc32(crc, buffer, size); - out = buffer; - uncompressed_size = size; - compressed_size = size; - } else { - error("unsupported file mode: 0%o (SHA1: %s)", mode, - sha1_to_hex(sha1)); - goto out; - } - - if (method == 8) { - deflated = zlib_deflate(buffer, size, &compressed_size); - if (deflated && compressed_size - 6 < size) { - /* ZLIB --> raw compressed data (see RFC 1950) */ - /* CMF and FLG ... */ - out = (unsigned char *)deflated + 2; - compressed_size -= 6; /* ... and ADLER32 */ - } else { - method = 0; - compressed_size = size; - } - } - - /* make sure we have enough free space in the dictionary */ - direntsize = sizeof(struct zip_dir_header) + pathlen; - while (zip_dir_size < zip_dir_offset + direntsize) { - zip_dir_size += ZIP_DIRECTORY_MIN_SIZE; - zip_dir = xrealloc(zip_dir, zip_dir_size); - } - - copy_le32(dirent.magic, 0x02014b50); - copy_le16(dirent.creator_version, 0); - copy_le16(dirent.version, 20); - copy_le16(dirent.flags, 0); - copy_le16(dirent.compression_method, method); - copy_le16(dirent.mtime, zip_time); - copy_le16(dirent.mdate, zip_date); - copy_le32(dirent.crc32, crc); - copy_le32(dirent.compressed_size, compressed_size); - copy_le32(dirent.size, uncompressed_size); - copy_le16(dirent.filename_length, pathlen); - copy_le16(dirent.extra_length, 0); - copy_le16(dirent.comment_length, 0); - copy_le16(dirent.disk, 0); - copy_le16(dirent.attr1, 0); - copy_le32(dirent.attr2, 0); - copy_le32(dirent.offset, zip_offset); - memcpy(zip_dir + zip_dir_offset, &dirent, sizeof(struct zip_dir_header)); - zip_dir_offset += sizeof(struct zip_dir_header); - memcpy(zip_dir + zip_dir_offset, path, pathlen); - zip_dir_offset += pathlen; - zip_dir_entries++; - - copy_le32(header.magic, 0x04034b50); - copy_le16(header.version, 20); - copy_le16(header.flags, 0); - copy_le16(header.compression_method, method); - copy_le16(header.mtime, zip_time); - copy_le16(header.mdate, zip_date); - copy_le32(header.crc32, crc); - copy_le32(header.compressed_size, compressed_size); - copy_le32(header.size, uncompressed_size); - copy_le16(header.filename_length, pathlen); - copy_le16(header.extra_length, 0); - write_or_die(1, &header, sizeof(struct zip_local_header)); - zip_offset += sizeof(struct zip_local_header); - write_or_die(1, path, pathlen); - zip_offset += pathlen; - if (compressed_size > 0) { - write_or_die(1, out, compressed_size); - zip_offset += compressed_size; - } - -out: - free(buffer); - free(deflated); - free(path); - - return result; -} - -static void write_zip_trailer(const unsigned char *sha1) -{ - struct zip_dir_trailer trailer; - - copy_le32(trailer.magic, 0x06054b50); - copy_le16(trailer.disk, 0); - copy_le16(trailer.directory_start_disk, 0); - copy_le16(trailer.entries_on_this_disk, zip_dir_entries); - copy_le16(trailer.entries, zip_dir_entries); - copy_le32(trailer.size, zip_dir_offset); - copy_le32(trailer.offset, zip_offset); - copy_le16(trailer.comment_length, sha1 ? 40 : 0); - - write_or_die(1, zip_dir, zip_dir_offset); - write_or_die(1, &trailer, sizeof(struct zip_dir_trailer)); - if (sha1) - write_or_die(1, sha1_to_hex(sha1), 40); -} - -static void dos_time(time_t *time, int *dos_date, int *dos_time) -{ - struct tm *t = localtime(time); - - *dos_date = t->tm_mday + (t->tm_mon + 1) * 32 + - (t->tm_year + 1900 - 1980) * 512; - *dos_time = t->tm_sec / 2 + t->tm_min * 32 + t->tm_hour * 2048; -} - -int write_zip_archive(struct archiver_args *args) -{ - int plen = strlen(args->base); - - dos_time(&args->time, &zip_date, &zip_time); - - zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE); - zip_dir_size = ZIP_DIRECTORY_MIN_SIZE; - verbose = args->verbose; - - if (args->base && plen > 0 && args->base[plen - 1] == '/') { - char *base = xstrdup(args->base); - int baselen = strlen(base); - - while (baselen > 0 && base[baselen - 1] == '/') - base[--baselen] = '\0'; - write_zip_entry(args->tree->object.sha1, "", 0, base, 040777, 0); - free(base); - } - read_tree_recursive(args->tree, args->base, plen, 0, - args->pathspec, write_zip_entry); - write_zip_trailer(args->commit_sha1); - - free(zip_dir); - - return 0; -} - -void *parse_extra_zip_args(int argc, const char **argv) -{ - for (; argc > 0; argc--, argv++) { - const char *arg = argv[0]; - - if (arg[0] == '-' && isdigit(arg[1]) && arg[2] == '\0') - zlib_compression_level = arg[1] - '0'; - else - die("Unknown argument for zip format: %s", arg); - } - return NULL; -} -- cgit v0.10.2-6-g49f6 From 8f41db8c370d535ed0132ef33d73e47edcc5af03 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sun, 24 Sep 2006 00:25:19 +0200 Subject: Deprecate git-resolve.sh Seriously, is anyone still using this thing? It's collecting dust and blocking the name for something potentially useful like a tool for user-friendly marking of resolved conflicts or resolving index conflicts. We've loved you when Git was young, now thank you and please go away. ;-) This makes git-resolve.sh print a big deprecation warning and sleep a bit for extra annoyance. It should be removed completely after the next release. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/git-resolve.sh b/git-resolve.sh index a7bc680..729ec65 100755 --- a/git-resolve.sh +++ b/git-resolve.sh @@ -5,6 +5,10 @@ # Resolve two trees. # +echo 'WARNING: This command is DEPRECATED and will be removed very soon.' >&2 +echo 'WARNING: Please use git-merge or git-pull instead.' >&2 +sleep 2 + USAGE=' ' . git-sh-setup -- cgit v0.10.2-6-g49f6 From a2f3db2f5de2a3667b0e038aa65e3e097e642e7d Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sun, 24 Sep 2006 00:18:41 +0200 Subject: gitweb: Consolidate escaping/validation of query string Consider: http://repo.or.cz/?p=glibc-cvs.git;a=tree;h=2609cb0411389325f4ee2854cc7159756eb0671e;hb=2609cb0411389325f4ee2854cc7159756eb0671e (click on the funny =__ify file) We ought to handle anything in filenames and I actually see no reason why we don't, modulo very little missing escaping that this patch hopefully also fixes. I have also made esc_param() escape [?=&;]. Not escaping [&;] was downright buggy and [?=] just feels better escaped. ;-) YMMV. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 3d06181..0693a83 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -212,19 +212,9 @@ if (defined $project) { } } +# We have to handle those containing any characters: our $file_name = $cgi->param('f'); -if (defined $file_name) { - if (!validate_input($file_name)) { - die_error(undef, "Invalid file parameter"); - } -} - our $file_parent = $cgi->param('fp'); -if (defined $file_parent) { - if (!validate_input($file_parent)) { - die_error(undef, "Invalid file parent parameter"); - } -} our $hash = $cgi->param('h'); if (defined $hash) { @@ -305,7 +295,7 @@ sub evaluate_path_info { $action ||= "blob_plain"; } $hash_base ||= validate_input($refname); - $file_name ||= validate_input($pathname); + $file_name ||= $pathname; } elsif (defined $refname) { # we got "project.git/branch" $action ||= "shortlog"; @@ -416,7 +406,7 @@ sub validate_input { # correct, but quoted slashes look too horrible in bookmarks sub esc_param { my $str = shift; - $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg; + $str =~ s/([^A-Za-z0-9\-_.~()\/:@])/sprintf("%%%02X", ord($1))/eg; $str =~ s/\+/%2B/g; $str =~ s/ /\+/g; return $str; @@ -1282,7 +1272,7 @@ sub git_header_html { if (defined $action) { $title .= "/$action"; if (defined $file_name) { - $title .= " - $file_name"; + $title .= " - " . esc_html($file_name); if ($action eq "tree" && $file_name !~ m|/$|) { $title .= "/"; } @@ -2430,7 +2420,7 @@ sub git_blame2 { if ($ftype !~ "blob") { die_error("400 Bad Request", "Object is not a blob"); } - open ($fd, "-|", git_cmd(), "blame", '-l', $file_name, $hash_base) + open ($fd, "-|", git_cmd(), "blame", '-l', '--', $file_name, $hash_base) or die_error(undef, "Open git-blame failed"); git_header_html(); my $formats_nav = @@ -3072,12 +3062,12 @@ sub git_blobdiff { if (defined $file_name) { if (defined $file_parent) { $diffinfo{'status'} = '2'; - $diffinfo{'from_file'} = $file_parent; - $diffinfo{'to_file'} = $file_name; + $diffinfo{'from_file'} = esc_html($file_parent); + $diffinfo{'to_file'} = esc_html($file_name); } else { # assume not renamed $diffinfo{'status'} = '1'; - $diffinfo{'from_file'} = $file_name; - $diffinfo{'to_file'} = $file_name; + $diffinfo{'from_file'} = esc_html($file_name); + $diffinfo{'to_file'} = esc_html($file_name); } } else { # no filename given $diffinfo{'status'} = '2'; @@ -3126,7 +3116,7 @@ sub git_blobdiff { -type => 'text/plain', -charset => 'utf-8', -expires => $expires, - -content_disposition => qq(inline; filename="${file_name}.patch")); + -content_disposition => qq(inline; filename=") . quotemeta($file_name) . qq(.patch")); print "X-Git-Url: " . $cgi->self_url() . "\n\n"; @@ -3576,7 +3566,7 @@ XML if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { next; } - my $file = validate_input(unquote($7)); + my $file = esc_html(unquote($7)); $file = decode("utf8", $file, Encode::FB_DEFAULT); print "$file
\n"; } -- cgit v0.10.2-6-g49f6 From ed1795fcc5f2aa3f105630429bcbed49c50053fa Mon Sep 17 00:00:00 2001 From: "Randal L. Schwartz" Date: Sat, 23 Sep 2006 17:20:45 -0700 Subject: builtin-upload-archive.c broken on openbsd Looks like ctype again. Gotta be careful with that on BSD releases: $ gmake prefix=/opt/git all GIT_VERSION = 1.4.2.GIT gcc -o builtin-upload-archive.o -c -g -O2 -Wall -I/usr/local/include -DSHA1_HEADER='' -DNO_STRCASESTR builtin-upload-archive.c In file included from /usr/include/sys/poll.h:54, from builtin-upload-archive.c:11: /usr/include/ctype.h:68: error: syntax error before ']' token /usr/include/ctype.h:69: error: syntax error before ']' token ... /usr/include/sys/poll.h:53:1: unterminated #ifndef /usr/include/sys/poll.h:28:1: unterminated #ifndef gmake: *** [builtin-upload-archive.o] Error 1 This fixes it. Signed-off-by: Junio C Hamano diff --git a/builtin-upload-archive.c b/builtin-upload-archive.c index 0596865..45c92e1 100644 --- a/builtin-upload-archive.c +++ b/builtin-upload-archive.c @@ -2,13 +2,13 @@ * Copyright (c) 2006 Franck Bui-Huu */ #include +#include +#include #include "cache.h" #include "builtin.h" #include "archive.h" #include "pkt-line.h" #include "sideband.h" -#include -#include static const char upload_archive_usage[] = "git-upload-archive "; -- cgit v0.10.2-6-g49f6 From 448c3ef144b2f528bb6dc50153ac13cc2abbc3c4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 22 Sep 2006 22:48:39 -0700 Subject: diff.c: second war on whitespace. This adds DIFF_WHITESPACE color class (default = reverse red) to colored diff output to let you catch common whitespace errors. - trailing whitespaces at the end of line - a space followed by a tab in the indent Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index 443e248..2464238 100644 --- a/diff.c +++ b/diff.c @@ -20,12 +20,13 @@ static int diff_use_color_default; static char diff_colors[][COLOR_MAXLEN] = { "\033[m", /* reset */ - "", /* normal */ - "\033[1m", /* bold */ - "\033[36m", /* cyan */ - "\033[31m", /* red */ - "\033[32m", /* green */ - "\033[33m" /* yellow */ + "", /* PLAIN (normal) */ + "\033[1m", /* METAINFO (bold) */ + "\033[36m", /* FRAGINFO (cyan) */ + "\033[31m", /* OLD (red) */ + "\033[32m", /* NEW (green) */ + "\033[33m", /* COMMIT (yellow) */ + "\033[41m", /* WHITESPACE (red background) */ }; static int parse_diff_color_slot(const char *var, int ofs) @@ -42,6 +43,8 @@ static int parse_diff_color_slot(const char *var, int ofs) return DIFF_FILE_NEW; if (!strcasecmp(var+ofs, "commit")) return DIFF_COMMIT; + if (!strcasecmp(var+ofs, "whitespace")) + return DIFF_WHITESPACE; die("bad config variable '%s'", var); } @@ -383,9 +386,89 @@ const char *diff_get_color(int diff_use_color, enum color_diff ix) return ""; } +static void emit_line(const char *set, const char *reset, const char *line, int len) +{ + if (len > 0 && line[len-1] == '\n') + len--; + fputs(set, stdout); + fwrite(line, len, 1, stdout); + puts(reset); +} + +static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len) +{ + int col0 = ecbdata->nparents; + int last_tab_in_indent = -1; + int last_space_in_indent = -1; + int i; + int tail = len; + int need_highlight_leading_space = 0; + const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE); + const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW); + + if (!*ws) { + emit_line(set, reset, line, len); + return; + } + + /* The line is a newly added line. Does it have funny leading + * whitespaces? In indent, SP should never precede a TAB. + */ + for (i = col0; i < len; i++) { + if (line[i] == '\t') { + last_tab_in_indent = i; + if (0 <= last_space_in_indent) + need_highlight_leading_space = 1; + } + else if (line[i] == ' ') + last_space_in_indent = i; + else + break; + } + fputs(set, stdout); + fwrite(line, col0, 1, stdout); + fputs(reset, stdout); + if (((i == len) || line[i] == '\n') && i != col0) { + /* The whole line was indent */ + emit_line(ws, reset, line + col0, len - col0); + return; + } + i = col0; + if (need_highlight_leading_space) { + while (i < last_tab_in_indent) { + if (line[i] == ' ') { + fputs(ws, stdout); + putchar(' '); + fputs(reset, stdout); + } + else + putchar(line[i]); + i++; + } + } + tail = len - 1; + if (line[tail] == '\n' && i < tail) + tail--; + while (i < tail) { + if (!isspace(line[tail])) + break; + tail--; + } + if ((i < tail && line[tail + 1] != '\n')) { + /* This has whitespace between tail+1..len */ + fputs(set, stdout); + fwrite(line + i, tail - i + 1, 1, stdout); + fputs(reset, stdout); + emit_line(ws, reset, line + tail + 1, len - tail - 1); + } + else + emit_line(set, reset, line + i, len - i); +} + static void fn_out_consume(void *priv, char *line, unsigned long len) { int i; + int color; struct emit_callback *ecbdata = priv; const char *set = diff_get_color(ecbdata->color_diff, DIFF_METAINFO); const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET); @@ -403,45 +486,52 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) ; if (2 <= i && i < len && line[i] == ' ') { ecbdata->nparents = i - 1; - set = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO); + emit_line(diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO), + reset, line, len); + return; } - else if (len < ecbdata->nparents) + + if (len < ecbdata->nparents) { set = reset; - else { - int nparents = ecbdata->nparents; - int color = DIFF_PLAIN; - if (ecbdata->diff_words && nparents != 1) - /* fall back to normal diff */ - free_diff_words_data(ecbdata); - if (ecbdata->diff_words) { - if (line[0] == '-') { - diff_words_append(line, len, - &ecbdata->diff_words->minus); - return; - } else if (line[0] == '+') { - diff_words_append(line, len, - &ecbdata->diff_words->plus); - return; - } - if (ecbdata->diff_words->minus.text.size || - ecbdata->diff_words->plus.text.size) - diff_words_show(ecbdata->diff_words); - line++; - len--; - } else - for (i = 0; i < nparents && len; i++) { - if (line[i] == '-') - color = DIFF_FILE_OLD; - else if (line[i] == '+') - color = DIFF_FILE_NEW; - } - set = diff_get_color(ecbdata->color_diff, color); + emit_line(reset, reset, line, len); + return; } - if (len > 0 && line[len-1] == '\n') + + color = DIFF_PLAIN; + if (ecbdata->diff_words && ecbdata->nparents != 1) + /* fall back to normal diff */ + free_diff_words_data(ecbdata); + if (ecbdata->diff_words) { + if (line[0] == '-') { + diff_words_append(line, len, + &ecbdata->diff_words->minus); + return; + } else if (line[0] == '+') { + diff_words_append(line, len, + &ecbdata->diff_words->plus); + return; + } + if (ecbdata->diff_words->minus.text.size || + ecbdata->diff_words->plus.text.size) + diff_words_show(ecbdata->diff_words); + line++; len--; - fputs (set, stdout); - fwrite (line, len, 1, stdout); - puts (reset); + emit_line(set, reset, line, len); + return; + } + for (i = 0; i < ecbdata->nparents && len; i++) { + if (line[i] == '-') + color = DIFF_FILE_OLD; + else if (line[i] == '+') + color = DIFF_FILE_NEW; + } + + if (color != DIFF_FILE_NEW) { + emit_line(diff_get_color(ecbdata->color_diff, color), + reset, line, len); + return; + } + emit_add_line(reset, ecbdata, line, len); } static char *pprint_rename(const char *a, const char *b) diff --git a/diff.h b/diff.h index b60a02e..3435fe7 100644 --- a/diff.h +++ b/diff.h @@ -86,6 +86,7 @@ enum color_diff { DIFF_FILE_OLD = 4, DIFF_FILE_NEW = 5, DIFF_COMMIT = 6, + DIFF_WHITESPACE = 7, }; const char *diff_get_color(int diff_use_color, enum color_diff ix); -- cgit v0.10.2-6-g49f6 From d0c25035df4897bb58422b4d64f00b54cf11f07e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 23 Sep 2006 00:37:19 -0700 Subject: git-apply: second war on whitespace. This makes --whitespace={warn,error,strip} option to also notice the leading whitespace errors in addition to the trailing whitespace errors. Spaces that are followed by a tab in indent are detected as errors, and --whitespace=strip option fixes them. Signed-off-by: Junio C Hamano diff --git a/builtin-apply.c b/builtin-apply.c index 25e90d8..de5f855 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -854,6 +854,49 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc return -1; } +static void check_whitespace(const char *line, int len) +{ + const char *err = "Adds trailing whitespace"; + int seen_space = 0; + int i; + + /* + * We know len is at least two, since we have a '+' and we + * checked that the last character was a '\n' before calling + * this function. That is, an addition of an empty line would + * check the '+' here. Sneaky... + */ + if (isspace(line[len-2])) + goto error; + + /* + * Make sure that there is no space followed by a tab in + * indentation. + */ + err = "Space in indent is followed by a tab"; + for (i = 1; i < len; i++) { + if (line[i] == '\t') { + if (seen_space) + goto error; + } + else if (line[i] == ' ') + seen_space = 1; + else + break; + } + return; + + error: + whitespace_error++; + if (squelch_whitespace_errors && + squelch_whitespace_errors < whitespace_error) + ; + else + fprintf(stderr, "%s.\n%s:%d:%.*s\n", + err, patch_input_file, linenr, len-2, line+1); +} + + /* * Parse a unified diff. Note that this really needs to parse each * fragment separately, since the only way to know the difference @@ -904,25 +947,8 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s trailing = 0; break; case '+': - /* - * We know len is at least two, since we have a '+' and - * we checked that the last character was a '\n' above. - * That is, an addition of an empty line would check - * the '+' here. Sneaky... - */ - if ((new_whitespace != nowarn_whitespace) && - isspace(line[len-2])) { - whitespace_error++; - if (squelch_whitespace_errors && - squelch_whitespace_errors < - whitespace_error) - ; - else { - fprintf(stderr, "Adds trailing whitespace.\n%s:%d:%.*s\n", - patch_input_file, - linenr, len-2, line+1); - } - } + if (new_whitespace != nowarn_whitespace) + check_whitespace(line, len); added++; newlines--; trailing = 0; @@ -1494,22 +1520,68 @@ static int apply_line(char *output, const char *patch, int plen) { /* plen is number of bytes to be copied from patch, * starting at patch+1 (patch[0] is '+'). Typically - * patch[plen] is '\n'. + * patch[plen] is '\n', unless this is the incomplete + * last line. */ + int i; int add_nl_to_tail = 0; - if ((new_whitespace == strip_whitespace) && - 1 < plen && isspace(patch[plen-1])) { + int fixed = 0; + int last_tab_in_indent = -1; + int last_space_in_indent = -1; + int need_fix_leading_space = 0; + char *buf; + + if ((new_whitespace != strip_whitespace) || !whitespace_error) { + memcpy(output, patch + 1, plen); + return plen; + } + + if (1 < plen && isspace(patch[plen-1])) { if (patch[plen] == '\n') add_nl_to_tail = 1; plen--; while (0 < plen && isspace(patch[plen])) plen--; - applied_after_stripping++; + fixed = 1; } - memcpy(output, patch + 1, plen); + + for (i = 1; i < plen; i++) { + char ch = patch[i]; + if (ch == '\t') { + last_tab_in_indent = i; + if (0 <= last_space_in_indent) + need_fix_leading_space = 1; + } + else if (ch == ' ') + last_space_in_indent = i; + else + break; + } + + buf = output; + if (need_fix_leading_space) { + /* between patch[1..last_tab_in_indent] strip the + * funny spaces, updating them to tab as needed. + */ + for (i = 1; i < last_tab_in_indent; i++, plen--) { + char ch = patch[i]; + if (ch != ' ') + *output++ = ch; + else if ((i % 8) == 0) + *output++ = '\t'; + } + fixed = 1; + i = last_tab_in_indent; + } + else + i = 1; + + memcpy(output, patch + i, plen); if (add_nl_to_tail) output[plen++] = '\n'; - return plen; + if (fixed) + applied_after_stripping++; + return output + plen - buf; } static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, int inaccurate_eof) -- cgit v0.10.2-6-g49f6 From 648ad18f5077295c4317f0784b29e09d2c0bac93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Santi=20B=C3=A9jar?= Date: Sat, 23 Sep 2006 12:05:43 +0200 Subject: Fetch: default remote repository from branch properties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If in branch "foo" and this in config: [branch "foo"] remote=bar "git fetch" = "git fetch bar" "git pull" = "git pull bar" Signed-off-by: Santi Béjar Signed-off-by: Junio C Hamano diff --git a/Documentation/config.txt b/Documentation/config.txt index bb2fbc3..04c5094 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -119,6 +119,9 @@ apply.whitespace:: Tells `git-apply` how to handle whitespaces, in the same way as the '--whitespace' option. See gitlink:git-apply[1]. +branch..remote:: + When in branch , it tells `git fetch` which remote to fetch. + pager.color:: A boolean to enable/disable colored output when the pager is in use (default is true). diff --git a/git-fetch.sh b/git-fetch.sh index 09a5d6c..50ad101 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -68,11 +68,10 @@ done case "$#" in 0) - test -f "$GIT_DIR/branches/origin" || - test -f "$GIT_DIR/remotes/origin" || - git-repo-config --get remote.origin.url >/dev/null || - die "Where do you want to fetch from today?" - set origin ;; + origin=$(get_default_remote) + test -n "$(get_remote_url ${origin})" || + die "Where do you want to fetch from today?" + set x $origin ; shift ;; esac remote_nick="$1" diff --git a/git-parse-remote.sh b/git-parse-remote.sh index 187f088..6999816 100755 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -68,6 +68,12 @@ get_remote_url () { esac } +get_default_remote () { + curr_branch=$(git-symbolic-ref HEAD | sed -e 's|^refs/heads/||') + origin=$(git-repo-config --get "branch.$curr_branch.remote") + echo ${origin:-origin} +} + get_remote_default_refs_for_push () { data_source=$(get_data_source "$1") case "$data_source" in -- cgit v0.10.2-6-g49f6 From 7be1d62c6f6b322d826d145762db0050057aeb69 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 23 Sep 2006 03:40:17 -0700 Subject: Add t5510 to test per branch configuration affecting git-fetch. Signed-off-by: Junio C Hamano diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh new file mode 100755 index 0000000..e71581a --- /dev/null +++ b/t/t5510-fetch.sh @@ -0,0 +1,44 @@ +#!/bin/sh +# Copyright (c) 2006, Junio C Hamano. + +test_description='Per branch config variables affects "git fetch". + +' + +. ./test-lib.sh + +D=`pwd` + +test_expect_success setup ' + echo >file original && + git add file && + git commit -a -m original' + +test_expect_success "clone and setup child repos" ' + git clone . one && + cd one && + echo >file updated by one && + git commit -a -m "updated by one" && + cd .. && + git clone . two && + cd two && + git repo-config branch.master.remote one && + { + echo "URL: ../one/.git/" + echo "Pull: refs/heads/master:refs/heads/one" + } >.git/remotes/one +' + +test_expect_success "fetch test" ' + cd "$D" && + echo >file updated by origin && + git commit -a -m "updated by origin" && + cd two && + git fetch && + test -f .git/refs/heads/one && + mine=`git rev-parse refs/heads/one` && + his=`cd ../one && git rev-parse refs/heads/master` && + test "z$mine" = "z$his" +' + +test_done -- cgit v0.10.2-6-g49f6 From 5372806a849cf117596b1f7c8c7d512c519f8092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Santi=20B=C3=A9jar?= Date: Sat, 23 Sep 2006 22:53:04 +0200 Subject: fetch: get the remote branches to merge from the branch properties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If in branch "foo" and this in config: [branch "foo"] merge=bar "git fetch": fetch from the default repository and program the "bar" branch to be merged with pull. Signed-off-by: Santi Béjar Signed-off-by: Junio C Hamano diff --git a/Documentation/config.txt b/Documentation/config.txt index 04c5094..98c1f3e 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -122,6 +122,10 @@ apply.whitespace:: branch..remote:: When in branch , it tells `git fetch` which remote to fetch. +branch..merge:: + When in branch , it tells `git fetch` the default remote branch + to be merged. + pager.color:: A boolean to enable/disable colored output when the pager is in use (default is true). diff --git a/git-parse-remote.sh b/git-parse-remote.sh index 6999816..c325ef7 100755 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -92,9 +92,22 @@ get_remote_default_refs_for_push () { # Subroutine to canonicalize remote:local notation. canon_refs_list_for_fetch () { - # Leave only the first one alone; add prefix . to the rest + # If called from get_remote_default_refs_for_fetch + # leave the branches in branch.${curr_branch}.merge alone, + # or the first one otherwise; add prefix . to the rest # to prevent the secondary branches to be merged by default. - dot_prefix= + merge_branches= + if test "$1" = "-d" + then + shift ; remote="$1" ; shift + if test "$remote" = "$(get_default_remote)" + then + curr_branch=$(git-symbolic-ref HEAD | \ + sed -e 's|^refs/heads/||') + merge_branches=$(git-repo-config \ + --get-all "branch.${curr_branch}.merge") + fi + fi for ref do force= @@ -107,6 +120,18 @@ canon_refs_list_for_fetch () { expr "z$ref" : 'z.*:' >/dev/null || ref="${ref}:" remote=$(expr "z$ref" : 'z\([^:]*\):') local=$(expr "z$ref" : 'z[^:]*:\(.*\)') + dot_prefix=. + if test -z "$merge_branches" + then + merge_branches=$remote + dot_prefix= + else + for merge_branch in $merge_branches + do + [ "$remote" = "$merge_branch" ] && + dot_prefix= && break + done + fi case "$remote" in '') remote=HEAD ;; refs/heads/* | refs/tags/* | refs/remotes/*) ;; @@ -126,7 +151,6 @@ canon_refs_list_for_fetch () { die "* refusing to create funny ref '$local_ref_name' locally" fi echo "${dot_prefix}${force}${remote}:${local}" - dot_prefix=. done } @@ -137,7 +161,7 @@ get_remote_default_refs_for_fetch () { '' | config-partial | branches-partial) echo "HEAD:" ;; config) - canon_refs_list_for_fetch \ + canon_refs_list_for_fetch -d "$1" \ $(git-repo-config --get-all "remote.$1.fetch") ;; branches) remote_branch=$(sed -ne '/#/s/.*#//p' "$GIT_DIR/branches/$1") @@ -145,10 +169,7 @@ get_remote_default_refs_for_fetch () { echo "refs/heads/${remote_branch}:refs/heads/$1" ;; remotes) - # This prefixes the second and later default refspecs - # with a '.', to signal git-fetch to mark them - # not-for-merge. - canon_refs_list_for_fetch $(sed -ne '/^Pull: */{ + canon_refs_list_for_fetch -d "$1" $(sed -ne '/^Pull: */{ s///p }' "$GIT_DIR/remotes/$1") ;; -- cgit v0.10.2-6-g49f6 From 6cc7c36d5e2fd89be596a164bcc2afede9d855d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Santi=20B=C3=A9jar?= Date: Sat, 23 Sep 2006 22:55:35 +0200 Subject: Add test for the default merges in fetch. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [jc: with minor fix-ups] Signed-off-by: Santi Béjar Signed-off-by: Junio C Hamano diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index e71581a..df0ae48 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -27,6 +27,16 @@ test_expect_success "clone and setup child repos" ' echo "URL: ../one/.git/" echo "Pull: refs/heads/master:refs/heads/one" } >.git/remotes/one + cd .. && + git clone . three && + cd three && + git repo-config branch.master.remote two && + git repo-config branch.master.merge refs/heads/one && + { + echo "URL: ../two/.git/" + echo "Pull: refs/heads/master:refs/heads/two" + echo "Pull: refs/heads/one:refs/heads/one" + } >.git/remotes/two ' test_expect_success "fetch test" ' @@ -41,4 +51,19 @@ test_expect_success "fetch test" ' test "z$mine" = "z$his" ' +test_expect_success "fetch test for-merge" ' + cd "$D" && + cd three && + git fetch && + test -f .git/refs/heads/two && + test -f .git/refs/heads/one && + master_in_two=`cd ../two && git rev-parse master` && + one_in_two=`cd ../two && git rev-parse one` && + { + echo "$master_in_two not-for-merge" + echo "$one_in_two " + } >expected && + cut -f -2 .git/FETCH_HEAD >actual && + diff expected actual' + test_done -- cgit v0.10.2-6-g49f6 From 81b84c42d645dd9c30b77f804abc07e128dc490b Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Sun, 24 Sep 2006 17:30:44 +0200 Subject: git-tar-tree: Remove duplicate git_config() call generate_tar() eventually calls write_tar_archive() which does all the "real" work and which also calls git_config(git_tar_config). We only need to do this once. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c index 437eb72..82b4951 100644 --- a/builtin-tar-tree.c +++ b/builtin-tar-tree.c @@ -267,8 +267,6 @@ static int generate_tar(int argc, const char **argv, const char *prefix) int result; char *base = NULL; - git_config(git_tar_config); - memset(&args, 0, sizeof(args)); if (argc != 2 && argc != 3) usage(tar_tree_usage); -- cgit v0.10.2-6-g49f6 From 3d74982f0b1f8895d27937aa6ed62c1ddd50a020 Mon Sep 17 00:00:00 2001 From: Rene Scharfe Date: Sun, 24 Sep 2006 17:31:10 +0200 Subject: git-tar-tree: Move code for git-archive --format=tar to archive-tar.c This patch doesn't change any functionality, it only moves code around. It makes seeing the few remaining lines of git-tar-tree code easier. ;-) Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 51fbe6a..59164b8 100644 --- a/Makefile +++ b/Makefile @@ -256,7 +256,7 @@ LIB_OBJS = \ fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \ write_or_die.o trace.o list-objects.o grep.o \ alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \ - color.o wt-status.o archive-zip.o + color.o wt-status.o archive-zip.o archive-tar.o BUILTIN_OBJS = \ builtin-add.o \ diff --git a/archive-tar.c b/archive-tar.c new file mode 100644 index 0000000..ff0f6e2 --- /dev/null +++ b/archive-tar.c @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2005, 2006 Rene Scharfe + */ +#include +#include "cache.h" +#include "commit.h" +#include "strbuf.h" +#include "tar.h" +#include "builtin.h" +#include "archive.h" + +#define RECORDSIZE (512) +#define BLOCKSIZE (RECORDSIZE * 20) + +static char block[BLOCKSIZE]; +static unsigned long offset; + +static time_t archive_time; +static int tar_umask; +static int verbose; + +/* writes out the whole block, but only if it is full */ +static void write_if_needed(void) +{ + if (offset == BLOCKSIZE) { + write_or_die(1, block, BLOCKSIZE); + offset = 0; + } +} + +/* + * queues up writes, so that all our write(2) calls write exactly one + * full block; pads writes to RECORDSIZE + */ +static void write_blocked(const void *data, unsigned long size) +{ + const char *buf = data; + unsigned long tail; + + if (offset) { + unsigned long chunk = BLOCKSIZE - offset; + if (size < chunk) + chunk = size; + memcpy(block + offset, buf, chunk); + size -= chunk; + offset += chunk; + buf += chunk; + write_if_needed(); + } + while (size >= BLOCKSIZE) { + write_or_die(1, buf, BLOCKSIZE); + size -= BLOCKSIZE; + buf += BLOCKSIZE; + } + if (size) { + memcpy(block + offset, buf, size); + offset += size; + } + tail = offset % RECORDSIZE; + if (tail) { + memset(block + offset, 0, RECORDSIZE - tail); + offset += RECORDSIZE - tail; + } + write_if_needed(); +} + +/* + * The end of tar archives is marked by 2*512 nul bytes and after that + * follows the rest of the block (if any). + */ +static void write_trailer(void) +{ + int tail = BLOCKSIZE - offset; + memset(block + offset, 0, tail); + write_or_die(1, block, BLOCKSIZE); + if (tail < 2 * RECORDSIZE) { + memset(block, 0, offset); + write_or_die(1, block, BLOCKSIZE); + } +} + +static void strbuf_append_string(struct strbuf *sb, const char *s) +{ + int slen = strlen(s); + int total = sb->len + slen; + if (total > sb->alloc) { + sb->buf = xrealloc(sb->buf, total); + sb->alloc = total; + } + memcpy(sb->buf + sb->len, s, slen); + sb->len = total; +} + +/* + * pax extended header records have the format "%u %s=%s\n". %u contains + * the size of the whole string (including the %u), the first %s is the + * keyword, the second one is the value. This function constructs such a + * string and appends it to a struct strbuf. + */ +static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword, + const char *value, unsigned int valuelen) +{ + char *p; + int len, total, tmp; + + /* "%u %s=%s\n" */ + len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1; + for (tmp = len; tmp > 9; tmp /= 10) + len++; + + total = sb->len + len; + if (total > sb->alloc) { + sb->buf = xrealloc(sb->buf, total); + sb->alloc = total; + } + + p = sb->buf; + p += sprintf(p, "%u %s=", len, keyword); + memcpy(p, value, valuelen); + p += valuelen; + *p = '\n'; + sb->len = total; +} + +static unsigned int ustar_header_chksum(const struct ustar_header *header) +{ + char *p = (char *)header; + unsigned int chksum = 0; + while (p < header->chksum) + chksum += *p++; + chksum += sizeof(header->chksum) * ' '; + p += sizeof(header->chksum); + while (p < (char *)header + sizeof(struct ustar_header)) + chksum += *p++; + return chksum; +} + +static int get_path_prefix(const struct strbuf *path, int maxlen) +{ + int i = path->len; + if (i > maxlen) + i = maxlen; + do { + i--; + } while (i > 0 && path->buf[i] != '/'); + return i; +} + +static void write_entry(const unsigned char *sha1, struct strbuf *path, + unsigned int mode, void *buffer, unsigned long size) +{ + struct ustar_header header; + struct strbuf ext_header; + + memset(&header, 0, sizeof(header)); + ext_header.buf = NULL; + ext_header.len = ext_header.alloc = 0; + + if (!sha1) { + *header.typeflag = TYPEFLAG_GLOBAL_HEADER; + mode = 0100666; + strcpy(header.name, "pax_global_header"); + } else if (!path) { + *header.typeflag = TYPEFLAG_EXT_HEADER; + mode = 0100666; + sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1)); + } else { + if (verbose) + fprintf(stderr, "%.*s\n", path->len, path->buf); + if (S_ISDIR(mode)) { + *header.typeflag = TYPEFLAG_DIR; + mode = (mode | 0777) & ~tar_umask; + } else if (S_ISLNK(mode)) { + *header.typeflag = TYPEFLAG_LNK; + mode |= 0777; + } else if (S_ISREG(mode)) { + *header.typeflag = TYPEFLAG_REG; + mode = (mode | ((mode & 0100) ? 0777 : 0666)) & ~tar_umask; + } else { + error("unsupported file mode: 0%o (SHA1: %s)", + mode, sha1_to_hex(sha1)); + return; + } + if (path->len > sizeof(header.name)) { + int plen = get_path_prefix(path, sizeof(header.prefix)); + int rest = path->len - plen - 1; + if (plen > 0 && rest <= sizeof(header.name)) { + memcpy(header.prefix, path->buf, plen); + memcpy(header.name, path->buf + plen + 1, rest); + } else { + sprintf(header.name, "%s.data", + sha1_to_hex(sha1)); + strbuf_append_ext_header(&ext_header, "path", + path->buf, path->len); + } + } else + memcpy(header.name, path->buf, path->len); + } + + if (S_ISLNK(mode) && buffer) { + if (size > sizeof(header.linkname)) { + sprintf(header.linkname, "see %s.paxheader", + sha1_to_hex(sha1)); + strbuf_append_ext_header(&ext_header, "linkpath", + buffer, size); + } else + memcpy(header.linkname, buffer, size); + } + + sprintf(header.mode, "%07o", mode & 07777); + sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0); + sprintf(header.mtime, "%011lo", archive_time); + + /* XXX: should we provide more meaningful info here? */ + sprintf(header.uid, "%07o", 0); + sprintf(header.gid, "%07o", 0); + strlcpy(header.uname, "git", sizeof(header.uname)); + strlcpy(header.gname, "git", sizeof(header.gname)); + sprintf(header.devmajor, "%07o", 0); + sprintf(header.devminor, "%07o", 0); + + memcpy(header.magic, "ustar", 6); + memcpy(header.version, "00", 2); + + sprintf(header.chksum, "%07o", ustar_header_chksum(&header)); + + if (ext_header.len > 0) { + write_entry(sha1, NULL, 0, ext_header.buf, ext_header.len); + free(ext_header.buf); + } + write_blocked(&header, sizeof(header)); + if (S_ISREG(mode) && buffer && size > 0) + write_blocked(buffer, size); +} + +static void write_global_extended_header(const unsigned char *sha1) +{ + struct strbuf ext_header; + ext_header.buf = NULL; + ext_header.len = ext_header.alloc = 0; + strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40); + write_entry(NULL, NULL, 0, ext_header.buf, ext_header.len); + free(ext_header.buf); +} + +static int git_tar_config(const char *var, const char *value) +{ + if (!strcmp(var, "tar.umask")) { + if (!strcmp(value, "user")) { + tar_umask = umask(0); + umask(tar_umask); + } else { + tar_umask = git_config_int(var, value); + } + return 0; + } + return git_default_config(var, value); +} + +static int write_tar_entry(const unsigned char *sha1, + const char *base, int baselen, + const char *filename, unsigned mode, int stage) +{ + static struct strbuf path; + int filenamelen = strlen(filename); + void *buffer; + char type[20]; + unsigned long size; + + if (!path.alloc) { + path.buf = xmalloc(PATH_MAX); + path.alloc = PATH_MAX; + path.len = path.eof = 0; + } + if (path.alloc < baselen + filenamelen) { + free(path.buf); + path.buf = xmalloc(baselen + filenamelen); + path.alloc = baselen + filenamelen; + } + memcpy(path.buf, base, baselen); + memcpy(path.buf + baselen, filename, filenamelen); + path.len = baselen + filenamelen; + if (S_ISDIR(mode)) { + strbuf_append_string(&path, "/"); + buffer = NULL; + size = 0; + } else { + buffer = read_sha1_file(sha1, type, &size); + if (!buffer) + die("cannot read %s", sha1_to_hex(sha1)); + } + + write_entry(sha1, &path, mode, buffer, size); + free(buffer); + + return READ_TREE_RECURSIVE; +} + +int write_tar_archive(struct archiver_args *args) +{ + int plen = args->base ? strlen(args->base) : 0; + + git_config(git_tar_config); + + archive_time = args->time; + verbose = args->verbose; + + if (args->commit_sha1) + write_global_extended_header(args->commit_sha1); + + if (args->base && plen > 0 && args->base[plen - 1] == '/') { + char *base = xstrdup(args->base); + int baselen = strlen(base); + + while (baselen > 0 && base[baselen - 1] == '/') + base[--baselen] = '\0'; + write_tar_entry(args->tree->object.sha1, "", 0, base, 040777, 0); + free(base); + } + read_tree_recursive(args->tree, args->base, plen, 0, + args->pathspec, write_tar_entry); + write_trailer(); + + return 0; +} diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c index 82b4951..aa370e3 100644 --- a/builtin-tar-tree.c +++ b/builtin-tar-tree.c @@ -4,7 +4,6 @@ #include #include "cache.h" #include "commit.h" -#include "strbuf.h" #include "tar.h" #include "builtin.h" #include "pkt-line.h" @@ -16,251 +15,6 @@ static const char tar_tree_usage[] = "git-tar-tree [--remote=] [basedir]"; -static char block[BLOCKSIZE]; -static unsigned long offset; - -static time_t archive_time; -static int tar_umask; -static int verbose; - -/* writes out the whole block, but only if it is full */ -static void write_if_needed(void) -{ - if (offset == BLOCKSIZE) { - write_or_die(1, block, BLOCKSIZE); - offset = 0; - } -} - -/* - * queues up writes, so that all our write(2) calls write exactly one - * full block; pads writes to RECORDSIZE - */ -static void write_blocked(const void *data, unsigned long size) -{ - const char *buf = data; - unsigned long tail; - - if (offset) { - unsigned long chunk = BLOCKSIZE - offset; - if (size < chunk) - chunk = size; - memcpy(block + offset, buf, chunk); - size -= chunk; - offset += chunk; - buf += chunk; - write_if_needed(); - } - while (size >= BLOCKSIZE) { - write_or_die(1, buf, BLOCKSIZE); - size -= BLOCKSIZE; - buf += BLOCKSIZE; - } - if (size) { - memcpy(block + offset, buf, size); - offset += size; - } - tail = offset % RECORDSIZE; - if (tail) { - memset(block + offset, 0, RECORDSIZE - tail); - offset += RECORDSIZE - tail; - } - write_if_needed(); -} - -/* - * The end of tar archives is marked by 2*512 nul bytes and after that - * follows the rest of the block (if any). - */ -static void write_trailer(void) -{ - int tail = BLOCKSIZE - offset; - memset(block + offset, 0, tail); - write_or_die(1, block, BLOCKSIZE); - if (tail < 2 * RECORDSIZE) { - memset(block, 0, offset); - write_or_die(1, block, BLOCKSIZE); - } -} - -static void strbuf_append_string(struct strbuf *sb, const char *s) -{ - int slen = strlen(s); - int total = sb->len + slen; - if (total > sb->alloc) { - sb->buf = xrealloc(sb->buf, total); - sb->alloc = total; - } - memcpy(sb->buf + sb->len, s, slen); - sb->len = total; -} - -/* - * pax extended header records have the format "%u %s=%s\n". %u contains - * the size of the whole string (including the %u), the first %s is the - * keyword, the second one is the value. This function constructs such a - * string and appends it to a struct strbuf. - */ -static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword, - const char *value, unsigned int valuelen) -{ - char *p; - int len, total, tmp; - - /* "%u %s=%s\n" */ - len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1; - for (tmp = len; tmp > 9; tmp /= 10) - len++; - - total = sb->len + len; - if (total > sb->alloc) { - sb->buf = xrealloc(sb->buf, total); - sb->alloc = total; - } - - p = sb->buf; - p += sprintf(p, "%u %s=", len, keyword); - memcpy(p, value, valuelen); - p += valuelen; - *p = '\n'; - sb->len = total; -} - -static unsigned int ustar_header_chksum(const struct ustar_header *header) -{ - char *p = (char *)header; - unsigned int chksum = 0; - while (p < header->chksum) - chksum += *p++; - chksum += sizeof(header->chksum) * ' '; - p += sizeof(header->chksum); - while (p < (char *)header + sizeof(struct ustar_header)) - chksum += *p++; - return chksum; -} - -static int get_path_prefix(const struct strbuf *path, int maxlen) -{ - int i = path->len; - if (i > maxlen) - i = maxlen; - do { - i--; - } while (i > 0 && path->buf[i] != '/'); - return i; -} - -static void write_entry(const unsigned char *sha1, struct strbuf *path, - unsigned int mode, void *buffer, unsigned long size) -{ - struct ustar_header header; - struct strbuf ext_header; - - memset(&header, 0, sizeof(header)); - ext_header.buf = NULL; - ext_header.len = ext_header.alloc = 0; - - if (!sha1) { - *header.typeflag = TYPEFLAG_GLOBAL_HEADER; - mode = 0100666; - strcpy(header.name, "pax_global_header"); - } else if (!path) { - *header.typeflag = TYPEFLAG_EXT_HEADER; - mode = 0100666; - sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1)); - } else { - if (verbose) - fprintf(stderr, "%.*s\n", path->len, path->buf); - if (S_ISDIR(mode)) { - *header.typeflag = TYPEFLAG_DIR; - mode = (mode | 0777) & ~tar_umask; - } else if (S_ISLNK(mode)) { - *header.typeflag = TYPEFLAG_LNK; - mode |= 0777; - } else if (S_ISREG(mode)) { - *header.typeflag = TYPEFLAG_REG; - mode = (mode | ((mode & 0100) ? 0777 : 0666)) & ~tar_umask; - } else { - error("unsupported file mode: 0%o (SHA1: %s)", - mode, sha1_to_hex(sha1)); - return; - } - if (path->len > sizeof(header.name)) { - int plen = get_path_prefix(path, sizeof(header.prefix)); - int rest = path->len - plen - 1; - if (plen > 0 && rest <= sizeof(header.name)) { - memcpy(header.prefix, path->buf, plen); - memcpy(header.name, path->buf + plen + 1, rest); - } else { - sprintf(header.name, "%s.data", - sha1_to_hex(sha1)); - strbuf_append_ext_header(&ext_header, "path", - path->buf, path->len); - } - } else - memcpy(header.name, path->buf, path->len); - } - - if (S_ISLNK(mode) && buffer) { - if (size > sizeof(header.linkname)) { - sprintf(header.linkname, "see %s.paxheader", - sha1_to_hex(sha1)); - strbuf_append_ext_header(&ext_header, "linkpath", - buffer, size); - } else - memcpy(header.linkname, buffer, size); - } - - sprintf(header.mode, "%07o", mode & 07777); - sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0); - sprintf(header.mtime, "%011lo", archive_time); - - /* XXX: should we provide more meaningful info here? */ - sprintf(header.uid, "%07o", 0); - sprintf(header.gid, "%07o", 0); - strlcpy(header.uname, "git", sizeof(header.uname)); - strlcpy(header.gname, "git", sizeof(header.gname)); - sprintf(header.devmajor, "%07o", 0); - sprintf(header.devminor, "%07o", 0); - - memcpy(header.magic, "ustar", 6); - memcpy(header.version, "00", 2); - - sprintf(header.chksum, "%07o", ustar_header_chksum(&header)); - - if (ext_header.len > 0) { - write_entry(sha1, NULL, 0, ext_header.buf, ext_header.len); - free(ext_header.buf); - } - write_blocked(&header, sizeof(header)); - if (S_ISREG(mode) && buffer && size > 0) - write_blocked(buffer, size); -} - -static void write_global_extended_header(const unsigned char *sha1) -{ - struct strbuf ext_header; - ext_header.buf = NULL; - ext_header.len = ext_header.alloc = 0; - strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40); - write_entry(NULL, NULL, 0, ext_header.buf, ext_header.len); - free(ext_header.buf); -} - -static int git_tar_config(const char *var, const char *value) -{ - if (!strcmp(var, "tar.umask")) { - if (!strcmp(value, "user")) { - tar_umask = umask(0); - umask(tar_umask); - } else { - tar_umask = git_config_int(var, value); - } - return 0; - } - return git_default_config(var, value); -} - static int generate_tar(int argc, const char **argv, const char *prefix) { struct archiver_args args; @@ -286,73 +40,6 @@ static int generate_tar(int argc, const char **argv, const char *prefix) return result; } -static int write_tar_entry(const unsigned char *sha1, - const char *base, int baselen, - const char *filename, unsigned mode, int stage) -{ - static struct strbuf path; - int filenamelen = strlen(filename); - void *buffer; - char type[20]; - unsigned long size; - - if (!path.alloc) { - path.buf = xmalloc(PATH_MAX); - path.alloc = PATH_MAX; - path.len = path.eof = 0; - } - if (path.alloc < baselen + filenamelen) { - free(path.buf); - path.buf = xmalloc(baselen + filenamelen); - path.alloc = baselen + filenamelen; - } - memcpy(path.buf, base, baselen); - memcpy(path.buf + baselen, filename, filenamelen); - path.len = baselen + filenamelen; - if (S_ISDIR(mode)) { - strbuf_append_string(&path, "/"); - buffer = NULL; - size = 0; - } else { - buffer = read_sha1_file(sha1, type, &size); - if (!buffer) - die("cannot read %s", sha1_to_hex(sha1)); - } - - write_entry(sha1, &path, mode, buffer, size); - free(buffer); - - return READ_TREE_RECURSIVE; -} - -int write_tar_archive(struct archiver_args *args) -{ - int plen = args->base ? strlen(args->base) : 0; - - git_config(git_tar_config); - - archive_time = args->time; - verbose = args->verbose; - - if (args->commit_sha1) - write_global_extended_header(args->commit_sha1); - - if (args->base && plen > 0 && args->base[plen - 1] == '/') { - char *base = xstrdup(args->base); - int baselen = strlen(base); - - while (baselen > 0 && base[baselen - 1] == '/') - base[--baselen] = '\0'; - write_tar_entry(args->tree->object.sha1, "", 0, base, 040777, 0); - free(base); - } - read_tree_recursive(args->tree, args->base, plen, 0, - args->pathspec, write_tar_entry); - write_trailer(); - - return 0; -} - static const char *exec = "git-upload-tar"; static int remote_tar(int argc, const char **argv) -- cgit v0.10.2-6-g49f6 From fd88d9c81ecdefd656b01f288ed9127b7abef53e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 24 Sep 2006 14:42:01 -0700 Subject: Remove upload-tar and make git-tar-tree a thin wrapper to git-archive The command now issues a big deprecation warning message and runs git-archive command with appropriate arguments. git-tar-tree $tree_ish $base always forces $base to be the leading directory name, so the --prefix parameter passed internally to git-archive is a slash appended to it, i.e. "--prefix=$base/". Signed-off-by: Junio C Hamano diff --git a/.gitignore b/.gitignore index 3ca66e4..284db5d 100644 --- a/.gitignore +++ b/.gitignore @@ -122,7 +122,6 @@ git-update-ref git-update-server-info git-upload-archive git-upload-pack -git-upload-tar git-var git-verify-pack git-verify-tag diff --git a/Documentation/git-tar-tree.txt b/Documentation/git-tar-tree.txt index 1e1c7fa..74a6fdd 100644 --- a/Documentation/git-tar-tree.txt +++ b/Documentation/git-tar-tree.txt @@ -12,6 +12,9 @@ SYNOPSIS DESCRIPTION ----------- +THIS COMMAND IS DEPRECATED. Use `git-archive` with `--format=tar` +option instead. + Creates a tar archive containing the tree structure for the named tree. When is specified it is added as a leading path to the files in the generated tar archive. diff --git a/Documentation/git-upload-tar.txt b/Documentation/git-upload-tar.txt deleted file mode 100644 index 394af62..0000000 --- a/Documentation/git-upload-tar.txt +++ /dev/null @@ -1,39 +0,0 @@ -git-upload-tar(1) -================= - -NAME ----- -git-upload-tar - Send tar archive - - -SYNOPSIS --------- -'git-upload-tar' - -DESCRIPTION ------------ -Invoked by 'git-tar-tree --remote' and sends a generated tar archive -to the other end over the git protocol. - -This command is usually not invoked directly by the end user. -The UI for the protocol is on the 'git-tar-tree' side, and the -program pair is meant to be used to get a tar archive from a -remote repository. - - -OPTIONS -------- -:: - The repository to get a tar archive from. - -Author ------- -Written by Junio C Hamano - -Documentation --------------- -Documentation by Junio C Hamano. - -GIT ---- -Part of the gitlink:git[7] suite diff --git a/Documentation/git.txt b/Documentation/git.txt index 744c38d..1bf5ef5 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -247,10 +247,6 @@ gitlink:git-upload-pack[1]:: Invoked by 'git-fetch-pack' to push what are asked for. -gitlink:git-upload-tar[1]:: - Invoked by 'git-tar-tree --remote' to return the tar - archive the other end asked for. - High-level commands (porcelain) ------------------------------- diff --git a/Makefile b/Makefile index 59164b8..28091d6 100644 --- a/Makefile +++ b/Makefile @@ -300,7 +300,6 @@ BUILTIN_OBJS = \ builtin-update-index.o \ builtin-update-ref.o \ builtin-upload-archive.o \ - builtin-upload-tar.o \ builtin-verify-pack.o \ builtin-write-tree.o diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c index aa370e3..4d4cfec 100644 --- a/builtin-tar-tree.c +++ b/builtin-tar-tree.c @@ -6,96 +6,66 @@ #include "commit.h" #include "tar.h" #include "builtin.h" -#include "pkt-line.h" -#include "archive.h" - -#define RECORDSIZE (512) -#define BLOCKSIZE (RECORDSIZE * 20) +#include "quote.h" static const char tar_tree_usage[] = -"git-tar-tree [--remote=] [basedir]"; +"git-tar-tree [--remote=] [basedir]\n" +"*** Note that this command is now deprecated; use git-archive instead."; -static int generate_tar(int argc, const char **argv, const char *prefix) +int cmd_tar_tree(int argc, const char **argv, const char *prefix) { - struct archiver_args args; - int result; - char *base = NULL; - - memset(&args, 0, sizeof(args)); - if (argc != 2 && argc != 3) - usage(tar_tree_usage); - if (argc == 3) { - int baselen = strlen(argv[2]); - base = xmalloc(baselen + 2); - memcpy(base, argv[2], baselen); - base[baselen] = '/'; - base[baselen + 1] = '\0'; + /* + * git-tar-tree is now a wrapper around git-archive --format=tar + * + * $0 --remote= arg... ==> + * git-archive --format=tar --remote= arg... + * $0 tree-ish ==> + * git-archive --format=tar tree-ish + * $0 tree-ish basedir ==> + * git-archive --format-tar --prefix=basedir tree-ish + */ + int i; + const char **nargv = xcalloc(sizeof(*nargv), argc + 2); + char *basedir_arg; + int nargc = 0; + + nargv[nargc++] = "git-archive"; + nargv[nargc++] = "--format=tar"; + + if (2 <= argc && !strncmp("--remote=", argv[1], 9)) { + nargv[nargc++] = argv[1]; + argv++; + argc--; } - args.base = base; - parse_treeish_arg(argv + 1, &args, NULL); - - result = write_tar_archive(&args); - free(base); - - return result; -} - -static const char *exec = "git-upload-tar"; - -static int remote_tar(int argc, const char **argv) -{ - int fd[2], ret, len; - pid_t pid; - char buf[1024]; - char *url; - - if (argc < 3 || 4 < argc) + switch (argc) { + default: usage(tar_tree_usage); - - /* --remote= */ - url = xstrdup(argv[1]+9); - pid = git_connect(fd, url, exec); - if (pid < 0) - return 1; - - packet_write(fd[1], "want %s\n", argv[2]); - if (argv[3]) - packet_write(fd[1], "base %s\n", argv[3]); - packet_flush(fd[1]); - - len = packet_read_line(fd[0], buf, sizeof(buf)); - if (!len) - die("git-tar-tree: expected ACK/NAK, got EOF"); - if (buf[len-1] == '\n') - buf[--len] = 0; - if (strcmp(buf, "ACK")) { - if (5 < len && !strncmp(buf, "NACK ", 5)) - die("git-tar-tree: NACK %s", buf + 5); - die("git-tar-tree: protocol error"); + break; + case 3: + /* base-path */ + basedir_arg = xmalloc(strlen(argv[2]) + 11); + sprintf(basedir_arg, "--prefix=%s/", argv[2]); + nargv[nargc++] = basedir_arg; + /* fallthru */ + case 2: + /* tree-ish */ + nargv[nargc++] = argv[1]; } - /* expect a flush */ - len = packet_read_line(fd[0], buf, sizeof(buf)); - if (len) - die("git-tar-tree: expected a flush"); - - /* Now, start reading from fd[0] and spit it out to stdout */ - ret = copy_fd(fd[0], 1); - close(fd[0]); - - ret |= finish_connect(pid); - return !!ret; -} - -int cmd_tar_tree(int argc, const char **argv, const char *prefix) -{ - if (argc < 2) - usage(tar_tree_usage); - if (!strncmp("--remote=", argv[1], 9)) - return remote_tar(argc, argv); - return generate_tar(argc, argv, prefix); + nargv[nargc] = NULL; + + fprintf(stderr, + "*** git-tar-tree is now deprecated.\n" + "*** Running git-archive instead.\n***"); + for (i = 0; i < nargc; i++) { + fputc(' ', stderr); + sq_quote_print(stderr, nargv[i]); + } + fputc('\n', stderr); + return cmd_archive(nargc, nargv, prefix); } /* ustar header + extended global header content */ +#define RECORDSIZE (512) #define HEADERSIZE (2 * RECORDSIZE) int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix) diff --git a/builtin-upload-tar.c b/builtin-upload-tar.c deleted file mode 100644 index 06a945a..0000000 --- a/builtin-upload-tar.c +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2006 Junio C Hamano - */ -#include "cache.h" -#include "pkt-line.h" -#include "exec_cmd.h" -#include "builtin.h" - -static const char upload_tar_usage[] = "git-upload-tar "; - -static int nak(const char *reason) -{ - packet_write(1, "NACK %s\n", reason); - packet_flush(1); - return 1; -} - -int cmd_upload_tar(int argc, const char **argv, const char *prefix) -{ - int len; - const char *dir = argv[1]; - char buf[8192]; - unsigned char sha1[20]; - char *base = NULL; - char hex[41]; - int ac; - const char *av[4]; - - if (argc != 2) - usage(upload_tar_usage); - if (strlen(dir) < sizeof(buf)-1) - strcpy(buf, dir); /* enter-repo smudges its argument */ - else - packet_write(1, "NACK insanely long repository name %s\n", dir); - if (!enter_repo(buf, 0)) { - packet_write(1, "NACK not a git archive %s\n", dir); - packet_flush(1); - return 1; - } - - len = packet_read_line(0, buf, sizeof(buf)); - if (len < 5 || strncmp("want ", buf, 5)) - return nak("expected want"); - if (buf[len-1] == '\n') - buf[--len] = 0; - if (get_sha1(buf + 5, sha1)) - return nak("expected sha1"); - strcpy(hex, sha1_to_hex(sha1)); - - len = packet_read_line(0, buf, sizeof(buf)); - if (len) { - if (len < 5 || strncmp("base ", buf, 5)) - return nak("expected (optional) base"); - if (buf[len-1] == '\n') - buf[--len] = 0; - base = xstrdup(buf + 5); - len = packet_read_line(0, buf, sizeof(buf)); - } - if (len) - return nak("expected flush"); - - packet_write(1, "ACK\n"); - packet_flush(1); - - ac = 0; - av[ac++] = "tar-tree"; - av[ac++] = hex; - if (base) - av[ac++] = base; - av[ac++] = NULL; - execv_git_cmd(av); - /* should it return that is an error */ - return 1; -} diff --git a/git.c b/git.c index 1686220..ae80e78 100644 --- a/git.c +++ b/git.c @@ -263,7 +263,6 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "update-index", cmd_update_index, RUN_SETUP }, { "update-ref", cmd_update_ref, RUN_SETUP }, { "upload-archive", cmd_upload_archive }, - { "upload-tar", cmd_upload_tar }, { "version", cmd_version }, { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER }, { "write-tree", cmd_write_tree, RUN_SETUP }, -- cgit v0.10.2-6-g49f6 From d0b353b1a7a2f5168a8e2c992403f2a7d1e323f8 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sun, 24 Sep 2006 22:31:11 -0400 Subject: git-repack: allow git-repack to run in subdirectory Now that we explicitly create all tmpfiles below $GIT_DIR, there's no reason to care about which directory we're in. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/git-repack.sh b/git-repack.sh index 9ae5092..f2c9071 100755 --- a/git-repack.sh +++ b/git-repack.sh @@ -4,6 +4,7 @@ # USAGE='[-a] [-d] [-f] [-l] [-n] [-q]' +SUBDIRECTORY_OK='Yes' . git-sh-setup no_update_info= all_into_one= remove_redundant= -- cgit v0.10.2-6-g49f6 From f7bae37f9ab2fdc567780d23495733c9af526e02 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Sun, 24 Sep 2006 22:50:15 -0400 Subject: Allow 'svn fetch' on '(no date)' revisions in Subversion. Added --ignore-nodate to allow 'git svn fetch' to import revisions from Subversion which have '(no date)' listed as the date of the revision. By default 'git svn fetch' will crash with an error when encountering such a revision. The user may restart the fetch operation by adding --ignore-nodate if they want to continue tracking that repository. I'm not entirely sure why a centralized version control system such as Subversion permits revisions to be created with absolutely no date/time associated with it but it apparently is possible as one of the Subversion repositories that I'm tracking with 'git svn' created such a revision on '(no date)' and by '(no user)'. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index b7b63f7..1cfa3e3 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -244,6 +244,18 @@ doing. repo-config key: svn.noignoreexternals +--ignore-nodate:: +Only used with the 'fetch' command. + +By default git-svn will crash if it tries to import a revision +from SVN which has '(no date)' listed as the date of the revision. +This is repository corruption on SVN's part, plain and simple. +But sometimes you really need those revisions anyway. + +If supplied git-svn will convert '(no date)' entries to the UNIX +epoch (midnight on Jan. 1, 1970). Yes, that's probably very wrong. +SVN was very wrong. + -- Basic Examples diff --git a/git-svn.perl b/git-svn.perl index 0290850..8a2ef99 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -52,7 +52,7 @@ my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit, $_template, $_shared, $_no_default_regex, $_no_graft_copy, $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit, $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m, - $_merge, $_strategy, $_dry_run); + $_merge, $_strategy, $_dry_run, $_ignore_nodate); my (@_branch_from, %tree_map, %users, %rusers, %equiv); my ($_svn_co_url_revs, $_svn_pg_peg_revs); my @repo_path_split_cache; @@ -65,6 +65,7 @@ my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext, 'repack:i' => \$_repack, 'no-metadata' => \$_no_metadata, 'quiet|q' => \$_q, + 'ignore-nodate' => \$_ignore_nodate, 'repack-flags|repack-args|repack-opts=s' => \$_repack_flags); my ($_trunk, $_tags, $_branches); @@ -1734,6 +1735,8 @@ sub next_log_entry { my $rev = $1; my ($author, $date, $lines) = split(/\s*\|\s*/, $_, 3); ($lines) = ($lines =~ /(\d+)/); + $date = '1970-01-01 00:00:00 +0000' + if ($_ignore_nodate && $date eq '(no date)'); my ($Y,$m,$d,$H,$M,$S,$tz) = ($date =~ /(\d{4})\-(\d\d)\-(\d\d)\s (\d\d)\:(\d\d)\:(\d\d)\s([\-\+]\d+)/x) -- cgit v0.10.2-6-g49f6 From 8815788e93c0a5a2e47e067dfa0764b17b8d1ddd Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Sun, 24 Sep 2006 23:04:55 -0400 Subject: Allow '(no author)' in git-svn's authors file. When trying to import an SVN revision which has no author the Git user may desire to relabel '(no author)' to another name and email address with their svn.authorsfile. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index 8a2ef99..017f45a 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2171,7 +2171,7 @@ sub load_authors { open my $authors, '<', $_authors or die "Can't open $_authors $!\n"; while (<$authors>) { chomp; - next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/; + next unless /^(\S+?|\(no author\))\s*=\s*(.+?)\s*<(.+)>\s*$/; my ($user, $name, $email) = ($1, $2, $3); $users{$user} = [$name, $email]; } -- cgit v0.10.2-6-g49f6 From 8391548e5e78677eb81f97334d998418802ea194 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sun, 24 Sep 2006 14:57:40 -0700 Subject: gitweb: fix over-eager application of esc_html(). Contents of %diffinfo hash should be quoted upon output but kept unquoted internally. Later users of this hash expect filenames to be filenames, not HTML gibberish. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 0693a83..66be619 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3062,12 +3062,12 @@ sub git_blobdiff { if (defined $file_name) { if (defined $file_parent) { $diffinfo{'status'} = '2'; - $diffinfo{'from_file'} = esc_html($file_parent); - $diffinfo{'to_file'} = esc_html($file_name); + $diffinfo{'from_file'} = $file_parent; + $diffinfo{'to_file'} = $file_name; } else { # assume not renamed $diffinfo{'status'} = '1'; - $diffinfo{'from_file'} = esc_html($file_name); - $diffinfo{'to_file'} = esc_html($file_name); + $diffinfo{'from_file'} = $file_name; + $diffinfo{'to_file'} = $file_name; } } else { # no filename given $diffinfo{'status'} = '2'; @@ -3136,8 +3136,8 @@ sub git_blobdiff { } else { while (my $line = <$fd>) { - $line =~ s!a/($hash|$hash_parent)!a/$diffinfo{'from_file'}!g; - $line =~ s!b/($hash|$hash_parent)!b/$diffinfo{'to_file'}!g; + $line =~ s!a/($hash|$hash_parent)!'a/'.esc_html($diffinfo{'from_file'})!eg; + $line =~ s!b/($hash|$hash_parent)!'b/'.esc_html($diffinfo{'to_file'})!eg; print $line; -- cgit v0.10.2-6-g49f6 From a06f678eb998862ea83b73e46ece32f99132935b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 24 Sep 2006 19:49:47 -0700 Subject: Deprecate merge-recursive.py This renames merge-recursive written in Python to merge-recursive-old, and makes merge-recur as a synonym to merge-recursive. We do not remove merge-recur yet, but we will remove merge-recur and merge-recursive-old in a few releases down the road. Signed-off-by: Junio C Hamano diff --git a/.gitignore b/.gitignore index 284db5d..25eb463 100644 --- a/.gitignore +++ b/.gitignore @@ -65,6 +65,7 @@ git-merge-one-file git-merge-ours git-merge-recur git-merge-recursive +git-merge-recursive-old git-merge-resolve git-merge-stupid git-mktag diff --git a/Makefile b/Makefile index 28091d6..c888c81 100644 --- a/Makefile +++ b/Makefile @@ -81,8 +81,6 @@ all: # Define NO_ACCURATE_DIFF if your diff program at least sometimes misses # a missing newline at the end of the file. # -# Define NO_PYTHON if you want to lose all benefits of the recursive merge. -# # Define COLLISION_CHECK below if you believe that SHA1's # 1461501637330902918203684832716283019655932542976 hashes do not give you # sufficient guarantee that no collisions between objects will ever happen. @@ -174,7 +172,7 @@ SCRIPT_PERL = \ git-send-email.perl git-svn.perl SCRIPT_PYTHON = \ - git-merge-recursive.py + git-merge-recursive-old.py SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ $(patsubst %.perl,%,$(SCRIPT_PERL)) \ @@ -199,7 +197,7 @@ PROGRAMS = \ git-upload-pack$X git-verify-pack$X \ git-pack-redundant$X git-var$X \ git-describe$X git-merge-tree$X git-blame$X git-imap-send$X \ - git-merge-recur$X \ + git-merge-recursive$X \ $(EXTRA_PROGRAMS) # Empty... @@ -570,7 +568,8 @@ LIB_OBJS += $(COMPAT_OBJS) export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir ### Build rules -all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi +all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi \ + git-merge-recur$X all: $(MAKE) -C templates @@ -585,6 +584,9 @@ git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS) GIT-CFLAGS help.o: common-cmds.h +git-merge-recur$X: git-merge-recursive$X + rm -f $@ && ln git-merge-recursive$X $@ + $(BUILT_INS): git$X rm -f $@ && ln git$X $@ @@ -722,11 +724,6 @@ git-http-push$X: revision.o http.o http-push.o $(GITLIBS) $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) -merge-recursive.o path-list.o: path-list.h -git-merge-recur$X: merge-recursive.o path-list.o $(GITLIBS) - $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ - $(LIBS) - $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H) $(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h) $(DIFF_OBJS): diffcore.h @@ -887,6 +884,7 @@ check-docs:: case "$$v" in \ git-merge-octopus | git-merge-ours | git-merge-recursive | \ git-merge-resolve | git-merge-stupid | git-merge-recur | \ + git-merge-recursive-old | \ git-ssh-pull | git-ssh-push ) continue ;; \ esac ; \ test -f "Documentation/$$v.txt" || \ diff --git a/configure.ac b/configure.ac index 511cac9..b1a5833 100644 --- a/configure.ac +++ b/configure.ac @@ -75,7 +75,6 @@ GIT_ARG_SET_PATH(shell) # Define PERL_PATH to provide path to Perl. GIT_ARG_SET_PATH(perl) # -# Define NO_PYTHON if you want to lose all benefits of the recursive merge. # Define PYTHON_PATH to provide path to Python. AC_ARG_WITH(python,[AS_HELP_STRING([--with-python=PATH], [provide PATH to python]) AS_HELP_STRING([--without-python], [don't use python scripts])], @@ -100,7 +99,6 @@ AC_PROG_CC AC_CHECK_TOOL(AR, ar, :) AC_CHECK_PROGS(TAR, [gtar tar]) # -# Define NO_PYTHON if you want to lose all benefits of the recursive merge. # Define PYTHON_PATH to provide path to Python. if test -z "$NO_PYTHON"; then if test -z "$PYTHON_PATH"; then diff --git a/git-merge-recursive-old.py b/git-merge-recursive-old.py new file mode 100755 index 0000000..4039435 --- /dev/null +++ b/git-merge-recursive-old.py @@ -0,0 +1,944 @@ +#!/usr/bin/python +# +# Copyright (C) 2005 Fredrik Kuivinen +# + +import sys +sys.path.append('''@@GIT_PYTHON_PATH@@''') + +import math, random, os, re, signal, tempfile, stat, errno, traceback +from heapq import heappush, heappop +from sets import Set + +from gitMergeCommon import * + +outputIndent = 0 +def output(*args): + sys.stdout.write(' '*outputIndent) + printList(args) + +originalIndexFile = os.environ.get('GIT_INDEX_FILE', + os.environ.get('GIT_DIR', '.git') + '/index') +temporaryIndexFile = os.environ.get('GIT_DIR', '.git') + \ + '/merge-recursive-tmp-index' +def setupIndex(temporary): + try: + os.unlink(temporaryIndexFile) + except OSError: + pass + if temporary: + newIndex = temporaryIndexFile + else: + newIndex = originalIndexFile + os.environ['GIT_INDEX_FILE'] = newIndex + +# This is a global variable which is used in a number of places but +# only written to in the 'merge' function. + +# cacheOnly == True => Don't leave any non-stage 0 entries in the cache and +# don't update the working directory. +# False => Leave unmerged entries in the cache and update +# the working directory. + +cacheOnly = False + +# The entry point to the merge code +# --------------------------------- + +def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0, ancestor=None): + '''Merge the commits h1 and h2, return the resulting virtual + commit object and a flag indicating the cleanness of the merge.''' + assert(isinstance(h1, Commit) and isinstance(h2, Commit)) + + global outputIndent + + output('Merging:') + output(h1) + output(h2) + sys.stdout.flush() + + if ancestor: + ca = [ancestor] + else: + assert(isinstance(graph, Graph)) + ca = getCommonAncestors(graph, h1, h2) + output('found', len(ca), 'common ancestor(s):') + for x in ca: + output(x) + sys.stdout.flush() + + mergedCA = ca[0] + for h in ca[1:]: + outputIndent = callDepth+1 + [mergedCA, dummy] = merge(mergedCA, h, + 'Temporary merge branch 1', + 'Temporary merge branch 2', + graph, callDepth+1) + outputIndent = callDepth + assert(isinstance(mergedCA, Commit)) + + global cacheOnly + if callDepth == 0: + setupIndex(False) + cacheOnly = False + else: + setupIndex(True) + runProgram(['git-read-tree', h1.tree()]) + cacheOnly = True + + [shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), mergedCA.tree(), + branch1Name, branch2Name) + + if graph and (clean or cacheOnly): + res = Commit(None, [h1, h2], tree=shaRes) + graph.addNode(res) + else: + res = None + + return [res, clean] + +getFilesRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)$', re.S) +def getFilesAndDirs(tree): + files = Set() + dirs = Set() + out = runProgram(['git-ls-tree', '-r', '-z', '-t', tree]) + for l in out.split('\0'): + m = getFilesRE.match(l) + if m: + if m.group(2) == 'tree': + dirs.add(m.group(4)) + elif m.group(2) == 'blob': + files.add(m.group(4)) + + return [files, dirs] + +# Those two global variables are used in a number of places but only +# written to in 'mergeTrees' and 'uniquePath'. They keep track of +# every file and directory in the two branches that are about to be +# merged. +currentFileSet = None +currentDirectorySet = None + +def mergeTrees(head, merge, common, branch1Name, branch2Name): + '''Merge the trees 'head' and 'merge' with the common ancestor + 'common'. The name of the head branch is 'branch1Name' and the name of + the merge branch is 'branch2Name'. Return a tuple (tree, cleanMerge) + where tree is the resulting tree and cleanMerge is True iff the + merge was clean.''' + + assert(isSha(head) and isSha(merge) and isSha(common)) + + if common == merge: + output('Already uptodate!') + return [head, True] + + if cacheOnly: + updateArg = '-i' + else: + updateArg = '-u' + + [out, code] = runProgram(['git-read-tree', updateArg, '-m', + common, head, merge], returnCode = True) + if code != 0: + die('git-read-tree:', out) + + [tree, code] = runProgram('git-write-tree', returnCode=True) + tree = tree.rstrip() + if code != 0: + global currentFileSet, currentDirectorySet + [currentFileSet, currentDirectorySet] = getFilesAndDirs(head) + [filesM, dirsM] = getFilesAndDirs(merge) + currentFileSet.union_update(filesM) + currentDirectorySet.union_update(dirsM) + + entries = unmergedCacheEntries() + renamesHead = getRenames(head, common, head, merge, entries) + renamesMerge = getRenames(merge, common, head, merge, entries) + + cleanMerge = processRenames(renamesHead, renamesMerge, + branch1Name, branch2Name) + for entry in entries: + if entry.processed: + continue + if not processEntry(entry, branch1Name, branch2Name): + cleanMerge = False + + if cleanMerge or cacheOnly: + tree = runProgram('git-write-tree').rstrip() + else: + tree = None + else: + cleanMerge = True + + return [tree, cleanMerge] + +# Low level file merging, update and removal +# ------------------------------------------ + +def mergeFile(oPath, oSha, oMode, aPath, aSha, aMode, bPath, bSha, bMode, + branch1Name, branch2Name): + + merge = False + clean = True + + if stat.S_IFMT(aMode) != stat.S_IFMT(bMode): + clean = False + if stat.S_ISREG(aMode): + mode = aMode + sha = aSha + else: + mode = bMode + sha = bSha + else: + if aSha != oSha and bSha != oSha: + merge = True + + if aMode == oMode: + mode = bMode + else: + mode = aMode + + if aSha == oSha: + sha = bSha + elif bSha == oSha: + sha = aSha + elif stat.S_ISREG(aMode): + assert(stat.S_ISREG(bMode)) + + orig = runProgram(['git-unpack-file', oSha]).rstrip() + src1 = runProgram(['git-unpack-file', aSha]).rstrip() + src2 = runProgram(['git-unpack-file', bSha]).rstrip() + try: + [out, code] = runProgram(['merge', + '-L', branch1Name + '/' + aPath, + '-L', 'orig/' + oPath, + '-L', branch2Name + '/' + bPath, + src1, orig, src2], returnCode=True) + except ProgramError, e: + print >>sys.stderr, e + die("Failed to execute 'merge'. merge(1) is used as the " + "file-level merge tool. Is 'merge' in your path?") + + sha = runProgram(['git-hash-object', '-t', 'blob', '-w', + src1]).rstrip() + + os.unlink(orig) + os.unlink(src1) + os.unlink(src2) + + clean = (code == 0) + else: + assert(stat.S_ISLNK(aMode) and stat.S_ISLNK(bMode)) + sha = aSha + + if aSha != bSha: + clean = False + + return [sha, mode, clean, merge] + +def updateFile(clean, sha, mode, path): + updateCache = cacheOnly or clean + updateWd = not cacheOnly + + return updateFileExt(sha, mode, path, updateCache, updateWd) + +def updateFileExt(sha, mode, path, updateCache, updateWd): + if cacheOnly: + updateWd = False + + if updateWd: + pathComponents = path.split('/') + for x in xrange(1, len(pathComponents)): + p = '/'.join(pathComponents[0:x]) + + try: + createDir = not stat.S_ISDIR(os.lstat(p).st_mode) + except OSError: + createDir = True + + if createDir: + try: + os.mkdir(p) + except OSError, e: + die("Couldn't create directory", p, e.strerror) + + prog = ['git-cat-file', 'blob', sha] + if stat.S_ISREG(mode): + try: + os.unlink(path) + except OSError: + pass + if mode & 0100: + mode = 0777 + else: + mode = 0666 + fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode) + proc = subprocess.Popen(prog, stdout=fd) + proc.wait() + os.close(fd) + elif stat.S_ISLNK(mode): + linkTarget = runProgram(prog) + os.symlink(linkTarget, path) + else: + assert(False) + + if updateWd and updateCache: + runProgram(['git-update-index', '--add', '--', path]) + elif updateCache: + runProgram(['git-update-index', '--add', '--cacheinfo', + '0%o' % mode, sha, path]) + +def setIndexStages(path, + oSHA1, oMode, + aSHA1, aMode, + bSHA1, bMode, + clear=True): + istring = [] + if clear: + istring.append("0 " + ("0" * 40) + "\t" + path + "\0") + if oMode: + istring.append("%o %s %d\t%s\0" % (oMode, oSHA1, 1, path)) + if aMode: + istring.append("%o %s %d\t%s\0" % (aMode, aSHA1, 2, path)) + if bMode: + istring.append("%o %s %d\t%s\0" % (bMode, bSHA1, 3, path)) + + runProgram(['git-update-index', '-z', '--index-info'], + input="".join(istring)) + +def removeFile(clean, path): + updateCache = cacheOnly or clean + updateWd = not cacheOnly + + if updateCache: + runProgram(['git-update-index', '--force-remove', '--', path]) + + if updateWd: + try: + os.unlink(path) + except OSError, e: + if e.errno != errno.ENOENT and e.errno != errno.EISDIR: + raise + try: + os.removedirs(os.path.dirname(path)) + except OSError: + pass + +def uniquePath(path, branch): + def fileExists(path): + try: + os.lstat(path) + return True + except OSError, e: + if e.errno == errno.ENOENT: + return False + else: + raise + + branch = branch.replace('/', '_') + newPath = path + '~' + branch + suffix = 0 + while newPath in currentFileSet or \ + newPath in currentDirectorySet or \ + fileExists(newPath): + suffix += 1 + newPath = path + '~' + branch + '_' + str(suffix) + currentFileSet.add(newPath) + return newPath + +# Cache entry management +# ---------------------- + +class CacheEntry: + def __init__(self, path): + class Stage: + def __init__(self): + self.sha1 = None + self.mode = None + + # Used for debugging only + def __str__(self): + if self.mode != None: + m = '0%o' % self.mode + else: + m = 'None' + + if self.sha1: + sha1 = self.sha1 + else: + sha1 = 'None' + return 'sha1: ' + sha1 + ' mode: ' + m + + self.stages = [Stage(), Stage(), Stage(), Stage()] + self.path = path + self.processed = False + + def __str__(self): + return 'path: ' + self.path + ' stages: ' + repr([str(x) for x in self.stages]) + +class CacheEntryContainer: + def __init__(self): + self.entries = {} + + def add(self, entry): + self.entries[entry.path] = entry + + def get(self, path): + return self.entries.get(path) + + def __iter__(self): + return self.entries.itervalues() + +unmergedRE = re.compile(r'^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S) +def unmergedCacheEntries(): + '''Create a dictionary mapping file names to CacheEntry + objects. The dictionary contains one entry for every path with a + non-zero stage entry.''' + + lines = runProgram(['git-ls-files', '-z', '--unmerged']).split('\0') + lines.pop() + + res = CacheEntryContainer() + for l in lines: + m = unmergedRE.match(l) + if m: + mode = int(m.group(1), 8) + sha1 = m.group(2) + stage = int(m.group(3)) + path = m.group(4) + + e = res.get(path) + if not e: + e = CacheEntry(path) + res.add(e) + + e.stages[stage].mode = mode + e.stages[stage].sha1 = sha1 + else: + die('Error: Merge program failed: Unexpected output from', + 'git-ls-files:', l) + return res + +lsTreeRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)\n$', re.S) +def getCacheEntry(path, origTree, aTree, bTree): + '''Returns a CacheEntry object which doesn't have to correspond to + a real cache entry in Git's index.''' + + def parse(out): + if out == '': + return [None, None] + else: + m = lsTreeRE.match(out) + if not m: + die('Unexpected output from git-ls-tree:', out) + elif m.group(2) == 'blob': + return [m.group(3), int(m.group(1), 8)] + else: + return [None, None] + + res = CacheEntry(path) + + [oSha, oMode] = parse(runProgram(['git-ls-tree', origTree, '--', path])) + [aSha, aMode] = parse(runProgram(['git-ls-tree', aTree, '--', path])) + [bSha, bMode] = parse(runProgram(['git-ls-tree', bTree, '--', path])) + + res.stages[1].sha1 = oSha + res.stages[1].mode = oMode + res.stages[2].sha1 = aSha + res.stages[2].mode = aMode + res.stages[3].sha1 = bSha + res.stages[3].mode = bMode + + return res + +# Rename detection and handling +# ----------------------------- + +class RenameEntry: + def __init__(self, + src, srcSha, srcMode, srcCacheEntry, + dst, dstSha, dstMode, dstCacheEntry, + score): + self.srcName = src + self.srcSha = srcSha + self.srcMode = srcMode + self.srcCacheEntry = srcCacheEntry + self.dstName = dst + self.dstSha = dstSha + self.dstMode = dstMode + self.dstCacheEntry = dstCacheEntry + self.score = score + + self.processed = False + +class RenameEntryContainer: + def __init__(self): + self.entriesSrc = {} + self.entriesDst = {} + + def add(self, entry): + self.entriesSrc[entry.srcName] = entry + self.entriesDst[entry.dstName] = entry + + def getSrc(self, path): + return self.entriesSrc.get(path) + + def getDst(self, path): + return self.entriesDst.get(path) + + def __iter__(self): + return self.entriesSrc.itervalues() + +parseDiffRenamesRE = re.compile('^:([0-7]+) ([0-7]+) ([0-9a-f]{40}) ([0-9a-f]{40}) R([0-9]*)$') +def getRenames(tree, oTree, aTree, bTree, cacheEntries): + '''Get information of all renames which occured between 'oTree' and + 'tree'. We need the three trees in the merge ('oTree', 'aTree' and + 'bTree') to be able to associate the correct cache entries with + the rename information. 'tree' is always equal to either aTree or bTree.''' + + assert(tree == aTree or tree == bTree) + inp = runProgram(['git-diff-tree', '-M', '--diff-filter=R', '-r', + '-z', oTree, tree]) + + ret = RenameEntryContainer() + try: + recs = inp.split("\0") + recs.pop() # remove last entry (which is '') + it = recs.__iter__() + while True: + rec = it.next() + m = parseDiffRenamesRE.match(rec) + + if not m: + die('Unexpected output from git-diff-tree:', rec) + + srcMode = int(m.group(1), 8) + dstMode = int(m.group(2), 8) + srcSha = m.group(3) + dstSha = m.group(4) + score = m.group(5) + src = it.next() + dst = it.next() + + srcCacheEntry = cacheEntries.get(src) + if not srcCacheEntry: + srcCacheEntry = getCacheEntry(src, oTree, aTree, bTree) + cacheEntries.add(srcCacheEntry) + + dstCacheEntry = cacheEntries.get(dst) + if not dstCacheEntry: + dstCacheEntry = getCacheEntry(dst, oTree, aTree, bTree) + cacheEntries.add(dstCacheEntry) + + ret.add(RenameEntry(src, srcSha, srcMode, srcCacheEntry, + dst, dstSha, dstMode, dstCacheEntry, + score)) + except StopIteration: + pass + return ret + +def fmtRename(src, dst): + srcPath = src.split('/') + dstPath = dst.split('/') + path = [] + endIndex = min(len(srcPath), len(dstPath)) - 1 + for x in range(0, endIndex): + if srcPath[x] == dstPath[x]: + path.append(srcPath[x]) + else: + endIndex = x + break + + if len(path) > 0: + return '/'.join(path) + \ + '/{' + '/'.join(srcPath[endIndex:]) + ' => ' + \ + '/'.join(dstPath[endIndex:]) + '}' + else: + return src + ' => ' + dst + +def processRenames(renamesA, renamesB, branchNameA, branchNameB): + srcNames = Set() + for x in renamesA: + srcNames.add(x.srcName) + for x in renamesB: + srcNames.add(x.srcName) + + cleanMerge = True + for path in srcNames: + if renamesA.getSrc(path): + renames1 = renamesA + renames2 = renamesB + branchName1 = branchNameA + branchName2 = branchNameB + else: + renames1 = renamesB + renames2 = renamesA + branchName1 = branchNameB + branchName2 = branchNameA + + ren1 = renames1.getSrc(path) + ren2 = renames2.getSrc(path) + + ren1.dstCacheEntry.processed = True + ren1.srcCacheEntry.processed = True + + if ren1.processed: + continue + + ren1.processed = True + + if ren2: + # Renamed in 1 and renamed in 2 + assert(ren1.srcName == ren2.srcName) + ren2.dstCacheEntry.processed = True + ren2.processed = True + + if ren1.dstName != ren2.dstName: + output('CONFLICT (rename/rename): Rename', + fmtRename(path, ren1.dstName), 'in branch', branchName1, + 'rename', fmtRename(path, ren2.dstName), 'in', + branchName2) + cleanMerge = False + + if ren1.dstName in currentDirectorySet: + dstName1 = uniquePath(ren1.dstName, branchName1) + output(ren1.dstName, 'is a directory in', branchName2, + 'adding as', dstName1, 'instead.') + removeFile(False, ren1.dstName) + else: + dstName1 = ren1.dstName + + if ren2.dstName in currentDirectorySet: + dstName2 = uniquePath(ren2.dstName, branchName2) + output(ren2.dstName, 'is a directory in', branchName1, + 'adding as', dstName2, 'instead.') + removeFile(False, ren2.dstName) + else: + dstName2 = ren2.dstName + setIndexStages(dstName1, + None, None, + ren1.dstSha, ren1.dstMode, + None, None) + setIndexStages(dstName2, + None, None, + None, None, + ren2.dstSha, ren2.dstMode) + + else: + removeFile(True, ren1.srcName) + + [resSha, resMode, clean, merge] = \ + mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode, + ren1.dstName, ren1.dstSha, ren1.dstMode, + ren2.dstName, ren2.dstSha, ren2.dstMode, + branchName1, branchName2) + + if merge or not clean: + output('Renaming', fmtRename(path, ren1.dstName)) + + if merge: + output('Auto-merging', ren1.dstName) + + if not clean: + output('CONFLICT (content): merge conflict in', + ren1.dstName) + cleanMerge = False + + if not cacheOnly: + setIndexStages(ren1.dstName, + ren1.srcSha, ren1.srcMode, + ren1.dstSha, ren1.dstMode, + ren2.dstSha, ren2.dstMode) + + updateFile(clean, resSha, resMode, ren1.dstName) + else: + removeFile(True, ren1.srcName) + + # Renamed in 1, maybe changed in 2 + if renamesA == renames1: + stage = 3 + else: + stage = 2 + + srcShaOtherBranch = ren1.srcCacheEntry.stages[stage].sha1 + srcModeOtherBranch = ren1.srcCacheEntry.stages[stage].mode + + dstShaOtherBranch = ren1.dstCacheEntry.stages[stage].sha1 + dstModeOtherBranch = ren1.dstCacheEntry.stages[stage].mode + + tryMerge = False + + if ren1.dstName in currentDirectorySet: + newPath = uniquePath(ren1.dstName, branchName1) + output('CONFLICT (rename/directory): Rename', + fmtRename(ren1.srcName, ren1.dstName), 'in', branchName1, + 'directory', ren1.dstName, 'added in', branchName2) + output('Renaming', ren1.srcName, 'to', newPath, 'instead') + cleanMerge = False + removeFile(False, ren1.dstName) + updateFile(False, ren1.dstSha, ren1.dstMode, newPath) + elif srcShaOtherBranch == None: + output('CONFLICT (rename/delete): Rename', + fmtRename(ren1.srcName, ren1.dstName), 'in', + branchName1, 'and deleted in', branchName2) + cleanMerge = False + updateFile(False, ren1.dstSha, ren1.dstMode, ren1.dstName) + elif dstShaOtherBranch: + newPath = uniquePath(ren1.dstName, branchName2) + output('CONFLICT (rename/add): Rename', + fmtRename(ren1.srcName, ren1.dstName), 'in', + branchName1 + '.', ren1.dstName, 'added in', branchName2) + output('Adding as', newPath, 'instead') + updateFile(False, dstShaOtherBranch, dstModeOtherBranch, newPath) + cleanMerge = False + tryMerge = True + elif renames2.getDst(ren1.dstName): + dst2 = renames2.getDst(ren1.dstName) + newPath1 = uniquePath(ren1.dstName, branchName1) + newPath2 = uniquePath(dst2.dstName, branchName2) + output('CONFLICT (rename/rename): Rename', + fmtRename(ren1.srcName, ren1.dstName), 'in', + branchName1+'. Rename', + fmtRename(dst2.srcName, dst2.dstName), 'in', branchName2) + output('Renaming', ren1.srcName, 'to', newPath1, 'and', + dst2.srcName, 'to', newPath2, 'instead') + removeFile(False, ren1.dstName) + updateFile(False, ren1.dstSha, ren1.dstMode, newPath1) + updateFile(False, dst2.dstSha, dst2.dstMode, newPath2) + dst2.processed = True + cleanMerge = False + else: + tryMerge = True + + if tryMerge: + + oName, oSHA1, oMode = ren1.srcName, ren1.srcSha, ren1.srcMode + aName, bName = ren1.dstName, ren1.srcName + aSHA1, bSHA1 = ren1.dstSha, srcShaOtherBranch + aMode, bMode = ren1.dstMode, srcModeOtherBranch + aBranch, bBranch = branchName1, branchName2 + + if renamesA != renames1: + aName, bName = bName, aName + aSHA1, bSHA1 = bSHA1, aSHA1 + aMode, bMode = bMode, aMode + aBranch, bBranch = bBranch, aBranch + + [resSha, resMode, clean, merge] = \ + mergeFile(oName, oSHA1, oMode, + aName, aSHA1, aMode, + bName, bSHA1, bMode, + aBranch, bBranch); + + if merge or not clean: + output('Renaming', fmtRename(ren1.srcName, ren1.dstName)) + + if merge: + output('Auto-merging', ren1.dstName) + + if not clean: + output('CONFLICT (rename/modify): Merge conflict in', + ren1.dstName) + cleanMerge = False + + if not cacheOnly: + setIndexStages(ren1.dstName, + oSHA1, oMode, + aSHA1, aMode, + bSHA1, bMode) + + updateFile(clean, resSha, resMode, ren1.dstName) + + return cleanMerge + +# Per entry merge function +# ------------------------ + +def processEntry(entry, branch1Name, branch2Name): + '''Merge one cache entry.''' + + debug('processing', entry.path, 'clean cache:', cacheOnly) + + cleanMerge = True + + path = entry.path + oSha = entry.stages[1].sha1 + oMode = entry.stages[1].mode + aSha = entry.stages[2].sha1 + aMode = entry.stages[2].mode + bSha = entry.stages[3].sha1 + bMode = entry.stages[3].mode + + assert(oSha == None or isSha(oSha)) + assert(aSha == None or isSha(aSha)) + assert(bSha == None or isSha(bSha)) + + assert(oMode == None or type(oMode) is int) + assert(aMode == None or type(aMode) is int) + assert(bMode == None or type(bMode) is int) + + if (oSha and (not aSha or not bSha)): + # + # Case A: Deleted in one + # + if (not aSha and not bSha) or \ + (aSha == oSha and not bSha) or \ + (not aSha and bSha == oSha): + # Deleted in both or deleted in one and unchanged in the other + if aSha: + output('Removing', path) + removeFile(True, path) + else: + # Deleted in one and changed in the other + cleanMerge = False + if not aSha: + output('CONFLICT (delete/modify):', path, 'deleted in', + branch1Name, 'and modified in', branch2Name + '.', + 'Version', branch2Name, 'of', path, 'left in tree.') + mode = bMode + sha = bSha + else: + output('CONFLICT (modify/delete):', path, 'deleted in', + branch2Name, 'and modified in', branch1Name + '.', + 'Version', branch1Name, 'of', path, 'left in tree.') + mode = aMode + sha = aSha + + updateFile(False, sha, mode, path) + + elif (not oSha and aSha and not bSha) or \ + (not oSha and not aSha and bSha): + # + # Case B: Added in one. + # + if aSha: + addBranch = branch1Name + otherBranch = branch2Name + mode = aMode + sha = aSha + conf = 'file/directory' + else: + addBranch = branch2Name + otherBranch = branch1Name + mode = bMode + sha = bSha + conf = 'directory/file' + + if path in currentDirectorySet: + cleanMerge = False + newPath = uniquePath(path, addBranch) + output('CONFLICT (' + conf + '):', + 'There is a directory with name', path, 'in', + otherBranch + '. Adding', path, 'as', newPath) + + removeFile(False, path) + updateFile(False, sha, mode, newPath) + else: + output('Adding', path) + updateFile(True, sha, mode, path) + + elif not oSha and aSha and bSha: + # + # Case C: Added in both (check for same permissions). + # + if aSha == bSha: + if aMode != bMode: + cleanMerge = False + output('CONFLICT: File', path, + 'added identically in both branches, but permissions', + 'conflict', '0%o' % aMode, '->', '0%o' % bMode) + output('CONFLICT: adding with permission:', '0%o' % aMode) + + updateFile(False, aSha, aMode, path) + else: + # This case is handled by git-read-tree + assert(False) + else: + cleanMerge = False + newPath1 = uniquePath(path, branch1Name) + newPath2 = uniquePath(path, branch2Name) + output('CONFLICT (add/add): File', path, + 'added non-identically in both branches. Adding as', + newPath1, 'and', newPath2, 'instead.') + removeFile(False, path) + updateFile(False, aSha, aMode, newPath1) + updateFile(False, bSha, bMode, newPath2) + + elif oSha and aSha and bSha: + # + # case D: Modified in both, but differently. + # + output('Auto-merging', path) + [sha, mode, clean, dummy] = \ + mergeFile(path, oSha, oMode, + path, aSha, aMode, + path, bSha, bMode, + branch1Name, branch2Name) + if clean: + updateFile(True, sha, mode, path) + else: + cleanMerge = False + output('CONFLICT (content): Merge conflict in', path) + + if cacheOnly: + updateFile(False, sha, mode, path) + else: + updateFileExt(sha, mode, path, updateCache=False, updateWd=True) + else: + die("ERROR: Fatal merge failure, shouldn't happen.") + + return cleanMerge + +def usage(): + die('Usage:', sys.argv[0], ' ... -- ..') + +# main entry point as merge strategy module +# The first parameters up to -- are merge bases, and the rest are heads. + +if len(sys.argv) < 4: + usage() + +bases = [] +for nextArg in xrange(1, len(sys.argv)): + if sys.argv[nextArg] == '--': + if len(sys.argv) != nextArg + 3: + die('Not handling anything other than two heads merge.') + try: + h1 = firstBranch = sys.argv[nextArg + 1] + h2 = secondBranch = sys.argv[nextArg + 2] + except IndexError: + usage() + break + else: + bases.append(sys.argv[nextArg]) + +print 'Merging', h1, 'with', h2 + +try: + h1 = runProgram(['git-rev-parse', '--verify', h1 + '^0']).rstrip() + h2 = runProgram(['git-rev-parse', '--verify', h2 + '^0']).rstrip() + + if len(bases) == 1: + base = runProgram(['git-rev-parse', '--verify', + bases[0] + '^0']).rstrip() + ancestor = Commit(base, None) + [dummy, clean] = merge(Commit(h1, None), Commit(h2, None), + firstBranch, secondBranch, None, 0, + ancestor) + else: + graph = buildGraph([h1, h2]) + [dummy, clean] = merge(graph.shaMap[h1], graph.shaMap[h2], + firstBranch, secondBranch, graph) + + print '' +except: + if isinstance(sys.exc_info()[1], SystemExit): + raise + else: + traceback.print_exc(None, sys.stderr) + sys.exit(2) + +if clean: + sys.exit(0) +else: + sys.exit(1) diff --git a/git-merge-recursive.py b/git-merge-recursive.py deleted file mode 100755 index 4039435..0000000 --- a/git-merge-recursive.py +++ /dev/null @@ -1,944 +0,0 @@ -#!/usr/bin/python -# -# Copyright (C) 2005 Fredrik Kuivinen -# - -import sys -sys.path.append('''@@GIT_PYTHON_PATH@@''') - -import math, random, os, re, signal, tempfile, stat, errno, traceback -from heapq import heappush, heappop -from sets import Set - -from gitMergeCommon import * - -outputIndent = 0 -def output(*args): - sys.stdout.write(' '*outputIndent) - printList(args) - -originalIndexFile = os.environ.get('GIT_INDEX_FILE', - os.environ.get('GIT_DIR', '.git') + '/index') -temporaryIndexFile = os.environ.get('GIT_DIR', '.git') + \ - '/merge-recursive-tmp-index' -def setupIndex(temporary): - try: - os.unlink(temporaryIndexFile) - except OSError: - pass - if temporary: - newIndex = temporaryIndexFile - else: - newIndex = originalIndexFile - os.environ['GIT_INDEX_FILE'] = newIndex - -# This is a global variable which is used in a number of places but -# only written to in the 'merge' function. - -# cacheOnly == True => Don't leave any non-stage 0 entries in the cache and -# don't update the working directory. -# False => Leave unmerged entries in the cache and update -# the working directory. - -cacheOnly = False - -# The entry point to the merge code -# --------------------------------- - -def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0, ancestor=None): - '''Merge the commits h1 and h2, return the resulting virtual - commit object and a flag indicating the cleanness of the merge.''' - assert(isinstance(h1, Commit) and isinstance(h2, Commit)) - - global outputIndent - - output('Merging:') - output(h1) - output(h2) - sys.stdout.flush() - - if ancestor: - ca = [ancestor] - else: - assert(isinstance(graph, Graph)) - ca = getCommonAncestors(graph, h1, h2) - output('found', len(ca), 'common ancestor(s):') - for x in ca: - output(x) - sys.stdout.flush() - - mergedCA = ca[0] - for h in ca[1:]: - outputIndent = callDepth+1 - [mergedCA, dummy] = merge(mergedCA, h, - 'Temporary merge branch 1', - 'Temporary merge branch 2', - graph, callDepth+1) - outputIndent = callDepth - assert(isinstance(mergedCA, Commit)) - - global cacheOnly - if callDepth == 0: - setupIndex(False) - cacheOnly = False - else: - setupIndex(True) - runProgram(['git-read-tree', h1.tree()]) - cacheOnly = True - - [shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), mergedCA.tree(), - branch1Name, branch2Name) - - if graph and (clean or cacheOnly): - res = Commit(None, [h1, h2], tree=shaRes) - graph.addNode(res) - else: - res = None - - return [res, clean] - -getFilesRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)$', re.S) -def getFilesAndDirs(tree): - files = Set() - dirs = Set() - out = runProgram(['git-ls-tree', '-r', '-z', '-t', tree]) - for l in out.split('\0'): - m = getFilesRE.match(l) - if m: - if m.group(2) == 'tree': - dirs.add(m.group(4)) - elif m.group(2) == 'blob': - files.add(m.group(4)) - - return [files, dirs] - -# Those two global variables are used in a number of places but only -# written to in 'mergeTrees' and 'uniquePath'. They keep track of -# every file and directory in the two branches that are about to be -# merged. -currentFileSet = None -currentDirectorySet = None - -def mergeTrees(head, merge, common, branch1Name, branch2Name): - '''Merge the trees 'head' and 'merge' with the common ancestor - 'common'. The name of the head branch is 'branch1Name' and the name of - the merge branch is 'branch2Name'. Return a tuple (tree, cleanMerge) - where tree is the resulting tree and cleanMerge is True iff the - merge was clean.''' - - assert(isSha(head) and isSha(merge) and isSha(common)) - - if common == merge: - output('Already uptodate!') - return [head, True] - - if cacheOnly: - updateArg = '-i' - else: - updateArg = '-u' - - [out, code] = runProgram(['git-read-tree', updateArg, '-m', - common, head, merge], returnCode = True) - if code != 0: - die('git-read-tree:', out) - - [tree, code] = runProgram('git-write-tree', returnCode=True) - tree = tree.rstrip() - if code != 0: - global currentFileSet, currentDirectorySet - [currentFileSet, currentDirectorySet] = getFilesAndDirs(head) - [filesM, dirsM] = getFilesAndDirs(merge) - currentFileSet.union_update(filesM) - currentDirectorySet.union_update(dirsM) - - entries = unmergedCacheEntries() - renamesHead = getRenames(head, common, head, merge, entries) - renamesMerge = getRenames(merge, common, head, merge, entries) - - cleanMerge = processRenames(renamesHead, renamesMerge, - branch1Name, branch2Name) - for entry in entries: - if entry.processed: - continue - if not processEntry(entry, branch1Name, branch2Name): - cleanMerge = False - - if cleanMerge or cacheOnly: - tree = runProgram('git-write-tree').rstrip() - else: - tree = None - else: - cleanMerge = True - - return [tree, cleanMerge] - -# Low level file merging, update and removal -# ------------------------------------------ - -def mergeFile(oPath, oSha, oMode, aPath, aSha, aMode, bPath, bSha, bMode, - branch1Name, branch2Name): - - merge = False - clean = True - - if stat.S_IFMT(aMode) != stat.S_IFMT(bMode): - clean = False - if stat.S_ISREG(aMode): - mode = aMode - sha = aSha - else: - mode = bMode - sha = bSha - else: - if aSha != oSha and bSha != oSha: - merge = True - - if aMode == oMode: - mode = bMode - else: - mode = aMode - - if aSha == oSha: - sha = bSha - elif bSha == oSha: - sha = aSha - elif stat.S_ISREG(aMode): - assert(stat.S_ISREG(bMode)) - - orig = runProgram(['git-unpack-file', oSha]).rstrip() - src1 = runProgram(['git-unpack-file', aSha]).rstrip() - src2 = runProgram(['git-unpack-file', bSha]).rstrip() - try: - [out, code] = runProgram(['merge', - '-L', branch1Name + '/' + aPath, - '-L', 'orig/' + oPath, - '-L', branch2Name + '/' + bPath, - src1, orig, src2], returnCode=True) - except ProgramError, e: - print >>sys.stderr, e - die("Failed to execute 'merge'. merge(1) is used as the " - "file-level merge tool. Is 'merge' in your path?") - - sha = runProgram(['git-hash-object', '-t', 'blob', '-w', - src1]).rstrip() - - os.unlink(orig) - os.unlink(src1) - os.unlink(src2) - - clean = (code == 0) - else: - assert(stat.S_ISLNK(aMode) and stat.S_ISLNK(bMode)) - sha = aSha - - if aSha != bSha: - clean = False - - return [sha, mode, clean, merge] - -def updateFile(clean, sha, mode, path): - updateCache = cacheOnly or clean - updateWd = not cacheOnly - - return updateFileExt(sha, mode, path, updateCache, updateWd) - -def updateFileExt(sha, mode, path, updateCache, updateWd): - if cacheOnly: - updateWd = False - - if updateWd: - pathComponents = path.split('/') - for x in xrange(1, len(pathComponents)): - p = '/'.join(pathComponents[0:x]) - - try: - createDir = not stat.S_ISDIR(os.lstat(p).st_mode) - except OSError: - createDir = True - - if createDir: - try: - os.mkdir(p) - except OSError, e: - die("Couldn't create directory", p, e.strerror) - - prog = ['git-cat-file', 'blob', sha] - if stat.S_ISREG(mode): - try: - os.unlink(path) - except OSError: - pass - if mode & 0100: - mode = 0777 - else: - mode = 0666 - fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode) - proc = subprocess.Popen(prog, stdout=fd) - proc.wait() - os.close(fd) - elif stat.S_ISLNK(mode): - linkTarget = runProgram(prog) - os.symlink(linkTarget, path) - else: - assert(False) - - if updateWd and updateCache: - runProgram(['git-update-index', '--add', '--', path]) - elif updateCache: - runProgram(['git-update-index', '--add', '--cacheinfo', - '0%o' % mode, sha, path]) - -def setIndexStages(path, - oSHA1, oMode, - aSHA1, aMode, - bSHA1, bMode, - clear=True): - istring = [] - if clear: - istring.append("0 " + ("0" * 40) + "\t" + path + "\0") - if oMode: - istring.append("%o %s %d\t%s\0" % (oMode, oSHA1, 1, path)) - if aMode: - istring.append("%o %s %d\t%s\0" % (aMode, aSHA1, 2, path)) - if bMode: - istring.append("%o %s %d\t%s\0" % (bMode, bSHA1, 3, path)) - - runProgram(['git-update-index', '-z', '--index-info'], - input="".join(istring)) - -def removeFile(clean, path): - updateCache = cacheOnly or clean - updateWd = not cacheOnly - - if updateCache: - runProgram(['git-update-index', '--force-remove', '--', path]) - - if updateWd: - try: - os.unlink(path) - except OSError, e: - if e.errno != errno.ENOENT and e.errno != errno.EISDIR: - raise - try: - os.removedirs(os.path.dirname(path)) - except OSError: - pass - -def uniquePath(path, branch): - def fileExists(path): - try: - os.lstat(path) - return True - except OSError, e: - if e.errno == errno.ENOENT: - return False - else: - raise - - branch = branch.replace('/', '_') - newPath = path + '~' + branch - suffix = 0 - while newPath in currentFileSet or \ - newPath in currentDirectorySet or \ - fileExists(newPath): - suffix += 1 - newPath = path + '~' + branch + '_' + str(suffix) - currentFileSet.add(newPath) - return newPath - -# Cache entry management -# ---------------------- - -class CacheEntry: - def __init__(self, path): - class Stage: - def __init__(self): - self.sha1 = None - self.mode = None - - # Used for debugging only - def __str__(self): - if self.mode != None: - m = '0%o' % self.mode - else: - m = 'None' - - if self.sha1: - sha1 = self.sha1 - else: - sha1 = 'None' - return 'sha1: ' + sha1 + ' mode: ' + m - - self.stages = [Stage(), Stage(), Stage(), Stage()] - self.path = path - self.processed = False - - def __str__(self): - return 'path: ' + self.path + ' stages: ' + repr([str(x) for x in self.stages]) - -class CacheEntryContainer: - def __init__(self): - self.entries = {} - - def add(self, entry): - self.entries[entry.path] = entry - - def get(self, path): - return self.entries.get(path) - - def __iter__(self): - return self.entries.itervalues() - -unmergedRE = re.compile(r'^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S) -def unmergedCacheEntries(): - '''Create a dictionary mapping file names to CacheEntry - objects. The dictionary contains one entry for every path with a - non-zero stage entry.''' - - lines = runProgram(['git-ls-files', '-z', '--unmerged']).split('\0') - lines.pop() - - res = CacheEntryContainer() - for l in lines: - m = unmergedRE.match(l) - if m: - mode = int(m.group(1), 8) - sha1 = m.group(2) - stage = int(m.group(3)) - path = m.group(4) - - e = res.get(path) - if not e: - e = CacheEntry(path) - res.add(e) - - e.stages[stage].mode = mode - e.stages[stage].sha1 = sha1 - else: - die('Error: Merge program failed: Unexpected output from', - 'git-ls-files:', l) - return res - -lsTreeRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)\n$', re.S) -def getCacheEntry(path, origTree, aTree, bTree): - '''Returns a CacheEntry object which doesn't have to correspond to - a real cache entry in Git's index.''' - - def parse(out): - if out == '': - return [None, None] - else: - m = lsTreeRE.match(out) - if not m: - die('Unexpected output from git-ls-tree:', out) - elif m.group(2) == 'blob': - return [m.group(3), int(m.group(1), 8)] - else: - return [None, None] - - res = CacheEntry(path) - - [oSha, oMode] = parse(runProgram(['git-ls-tree', origTree, '--', path])) - [aSha, aMode] = parse(runProgram(['git-ls-tree', aTree, '--', path])) - [bSha, bMode] = parse(runProgram(['git-ls-tree', bTree, '--', path])) - - res.stages[1].sha1 = oSha - res.stages[1].mode = oMode - res.stages[2].sha1 = aSha - res.stages[2].mode = aMode - res.stages[3].sha1 = bSha - res.stages[3].mode = bMode - - return res - -# Rename detection and handling -# ----------------------------- - -class RenameEntry: - def __init__(self, - src, srcSha, srcMode, srcCacheEntry, - dst, dstSha, dstMode, dstCacheEntry, - score): - self.srcName = src - self.srcSha = srcSha - self.srcMode = srcMode - self.srcCacheEntry = srcCacheEntry - self.dstName = dst - self.dstSha = dstSha - self.dstMode = dstMode - self.dstCacheEntry = dstCacheEntry - self.score = score - - self.processed = False - -class RenameEntryContainer: - def __init__(self): - self.entriesSrc = {} - self.entriesDst = {} - - def add(self, entry): - self.entriesSrc[entry.srcName] = entry - self.entriesDst[entry.dstName] = entry - - def getSrc(self, path): - return self.entriesSrc.get(path) - - def getDst(self, path): - return self.entriesDst.get(path) - - def __iter__(self): - return self.entriesSrc.itervalues() - -parseDiffRenamesRE = re.compile('^:([0-7]+) ([0-7]+) ([0-9a-f]{40}) ([0-9a-f]{40}) R([0-9]*)$') -def getRenames(tree, oTree, aTree, bTree, cacheEntries): - '''Get information of all renames which occured between 'oTree' and - 'tree'. We need the three trees in the merge ('oTree', 'aTree' and - 'bTree') to be able to associate the correct cache entries with - the rename information. 'tree' is always equal to either aTree or bTree.''' - - assert(tree == aTree or tree == bTree) - inp = runProgram(['git-diff-tree', '-M', '--diff-filter=R', '-r', - '-z', oTree, tree]) - - ret = RenameEntryContainer() - try: - recs = inp.split("\0") - recs.pop() # remove last entry (which is '') - it = recs.__iter__() - while True: - rec = it.next() - m = parseDiffRenamesRE.match(rec) - - if not m: - die('Unexpected output from git-diff-tree:', rec) - - srcMode = int(m.group(1), 8) - dstMode = int(m.group(2), 8) - srcSha = m.group(3) - dstSha = m.group(4) - score = m.group(5) - src = it.next() - dst = it.next() - - srcCacheEntry = cacheEntries.get(src) - if not srcCacheEntry: - srcCacheEntry = getCacheEntry(src, oTree, aTree, bTree) - cacheEntries.add(srcCacheEntry) - - dstCacheEntry = cacheEntries.get(dst) - if not dstCacheEntry: - dstCacheEntry = getCacheEntry(dst, oTree, aTree, bTree) - cacheEntries.add(dstCacheEntry) - - ret.add(RenameEntry(src, srcSha, srcMode, srcCacheEntry, - dst, dstSha, dstMode, dstCacheEntry, - score)) - except StopIteration: - pass - return ret - -def fmtRename(src, dst): - srcPath = src.split('/') - dstPath = dst.split('/') - path = [] - endIndex = min(len(srcPath), len(dstPath)) - 1 - for x in range(0, endIndex): - if srcPath[x] == dstPath[x]: - path.append(srcPath[x]) - else: - endIndex = x - break - - if len(path) > 0: - return '/'.join(path) + \ - '/{' + '/'.join(srcPath[endIndex:]) + ' => ' + \ - '/'.join(dstPath[endIndex:]) + '}' - else: - return src + ' => ' + dst - -def processRenames(renamesA, renamesB, branchNameA, branchNameB): - srcNames = Set() - for x in renamesA: - srcNames.add(x.srcName) - for x in renamesB: - srcNames.add(x.srcName) - - cleanMerge = True - for path in srcNames: - if renamesA.getSrc(path): - renames1 = renamesA - renames2 = renamesB - branchName1 = branchNameA - branchName2 = branchNameB - else: - renames1 = renamesB - renames2 = renamesA - branchName1 = branchNameB - branchName2 = branchNameA - - ren1 = renames1.getSrc(path) - ren2 = renames2.getSrc(path) - - ren1.dstCacheEntry.processed = True - ren1.srcCacheEntry.processed = True - - if ren1.processed: - continue - - ren1.processed = True - - if ren2: - # Renamed in 1 and renamed in 2 - assert(ren1.srcName == ren2.srcName) - ren2.dstCacheEntry.processed = True - ren2.processed = True - - if ren1.dstName != ren2.dstName: - output('CONFLICT (rename/rename): Rename', - fmtRename(path, ren1.dstName), 'in branch', branchName1, - 'rename', fmtRename(path, ren2.dstName), 'in', - branchName2) - cleanMerge = False - - if ren1.dstName in currentDirectorySet: - dstName1 = uniquePath(ren1.dstName, branchName1) - output(ren1.dstName, 'is a directory in', branchName2, - 'adding as', dstName1, 'instead.') - removeFile(False, ren1.dstName) - else: - dstName1 = ren1.dstName - - if ren2.dstName in currentDirectorySet: - dstName2 = uniquePath(ren2.dstName, branchName2) - output(ren2.dstName, 'is a directory in', branchName1, - 'adding as', dstName2, 'instead.') - removeFile(False, ren2.dstName) - else: - dstName2 = ren2.dstName - setIndexStages(dstName1, - None, None, - ren1.dstSha, ren1.dstMode, - None, None) - setIndexStages(dstName2, - None, None, - None, None, - ren2.dstSha, ren2.dstMode) - - else: - removeFile(True, ren1.srcName) - - [resSha, resMode, clean, merge] = \ - mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode, - ren1.dstName, ren1.dstSha, ren1.dstMode, - ren2.dstName, ren2.dstSha, ren2.dstMode, - branchName1, branchName2) - - if merge or not clean: - output('Renaming', fmtRename(path, ren1.dstName)) - - if merge: - output('Auto-merging', ren1.dstName) - - if not clean: - output('CONFLICT (content): merge conflict in', - ren1.dstName) - cleanMerge = False - - if not cacheOnly: - setIndexStages(ren1.dstName, - ren1.srcSha, ren1.srcMode, - ren1.dstSha, ren1.dstMode, - ren2.dstSha, ren2.dstMode) - - updateFile(clean, resSha, resMode, ren1.dstName) - else: - removeFile(True, ren1.srcName) - - # Renamed in 1, maybe changed in 2 - if renamesA == renames1: - stage = 3 - else: - stage = 2 - - srcShaOtherBranch = ren1.srcCacheEntry.stages[stage].sha1 - srcModeOtherBranch = ren1.srcCacheEntry.stages[stage].mode - - dstShaOtherBranch = ren1.dstCacheEntry.stages[stage].sha1 - dstModeOtherBranch = ren1.dstCacheEntry.stages[stage].mode - - tryMerge = False - - if ren1.dstName in currentDirectorySet: - newPath = uniquePath(ren1.dstName, branchName1) - output('CONFLICT (rename/directory): Rename', - fmtRename(ren1.srcName, ren1.dstName), 'in', branchName1, - 'directory', ren1.dstName, 'added in', branchName2) - output('Renaming', ren1.srcName, 'to', newPath, 'instead') - cleanMerge = False - removeFile(False, ren1.dstName) - updateFile(False, ren1.dstSha, ren1.dstMode, newPath) - elif srcShaOtherBranch == None: - output('CONFLICT (rename/delete): Rename', - fmtRename(ren1.srcName, ren1.dstName), 'in', - branchName1, 'and deleted in', branchName2) - cleanMerge = False - updateFile(False, ren1.dstSha, ren1.dstMode, ren1.dstName) - elif dstShaOtherBranch: - newPath = uniquePath(ren1.dstName, branchName2) - output('CONFLICT (rename/add): Rename', - fmtRename(ren1.srcName, ren1.dstName), 'in', - branchName1 + '.', ren1.dstName, 'added in', branchName2) - output('Adding as', newPath, 'instead') - updateFile(False, dstShaOtherBranch, dstModeOtherBranch, newPath) - cleanMerge = False - tryMerge = True - elif renames2.getDst(ren1.dstName): - dst2 = renames2.getDst(ren1.dstName) - newPath1 = uniquePath(ren1.dstName, branchName1) - newPath2 = uniquePath(dst2.dstName, branchName2) - output('CONFLICT (rename/rename): Rename', - fmtRename(ren1.srcName, ren1.dstName), 'in', - branchName1+'. Rename', - fmtRename(dst2.srcName, dst2.dstName), 'in', branchName2) - output('Renaming', ren1.srcName, 'to', newPath1, 'and', - dst2.srcName, 'to', newPath2, 'instead') - removeFile(False, ren1.dstName) - updateFile(False, ren1.dstSha, ren1.dstMode, newPath1) - updateFile(False, dst2.dstSha, dst2.dstMode, newPath2) - dst2.processed = True - cleanMerge = False - else: - tryMerge = True - - if tryMerge: - - oName, oSHA1, oMode = ren1.srcName, ren1.srcSha, ren1.srcMode - aName, bName = ren1.dstName, ren1.srcName - aSHA1, bSHA1 = ren1.dstSha, srcShaOtherBranch - aMode, bMode = ren1.dstMode, srcModeOtherBranch - aBranch, bBranch = branchName1, branchName2 - - if renamesA != renames1: - aName, bName = bName, aName - aSHA1, bSHA1 = bSHA1, aSHA1 - aMode, bMode = bMode, aMode - aBranch, bBranch = bBranch, aBranch - - [resSha, resMode, clean, merge] = \ - mergeFile(oName, oSHA1, oMode, - aName, aSHA1, aMode, - bName, bSHA1, bMode, - aBranch, bBranch); - - if merge or not clean: - output('Renaming', fmtRename(ren1.srcName, ren1.dstName)) - - if merge: - output('Auto-merging', ren1.dstName) - - if not clean: - output('CONFLICT (rename/modify): Merge conflict in', - ren1.dstName) - cleanMerge = False - - if not cacheOnly: - setIndexStages(ren1.dstName, - oSHA1, oMode, - aSHA1, aMode, - bSHA1, bMode) - - updateFile(clean, resSha, resMode, ren1.dstName) - - return cleanMerge - -# Per entry merge function -# ------------------------ - -def processEntry(entry, branch1Name, branch2Name): - '''Merge one cache entry.''' - - debug('processing', entry.path, 'clean cache:', cacheOnly) - - cleanMerge = True - - path = entry.path - oSha = entry.stages[1].sha1 - oMode = entry.stages[1].mode - aSha = entry.stages[2].sha1 - aMode = entry.stages[2].mode - bSha = entry.stages[3].sha1 - bMode = entry.stages[3].mode - - assert(oSha == None or isSha(oSha)) - assert(aSha == None or isSha(aSha)) - assert(bSha == None or isSha(bSha)) - - assert(oMode == None or type(oMode) is int) - assert(aMode == None or type(aMode) is int) - assert(bMode == None or type(bMode) is int) - - if (oSha and (not aSha or not bSha)): - # - # Case A: Deleted in one - # - if (not aSha and not bSha) or \ - (aSha == oSha and not bSha) or \ - (not aSha and bSha == oSha): - # Deleted in both or deleted in one and unchanged in the other - if aSha: - output('Removing', path) - removeFile(True, path) - else: - # Deleted in one and changed in the other - cleanMerge = False - if not aSha: - output('CONFLICT (delete/modify):', path, 'deleted in', - branch1Name, 'and modified in', branch2Name + '.', - 'Version', branch2Name, 'of', path, 'left in tree.') - mode = bMode - sha = bSha - else: - output('CONFLICT (modify/delete):', path, 'deleted in', - branch2Name, 'and modified in', branch1Name + '.', - 'Version', branch1Name, 'of', path, 'left in tree.') - mode = aMode - sha = aSha - - updateFile(False, sha, mode, path) - - elif (not oSha and aSha and not bSha) or \ - (not oSha and not aSha and bSha): - # - # Case B: Added in one. - # - if aSha: - addBranch = branch1Name - otherBranch = branch2Name - mode = aMode - sha = aSha - conf = 'file/directory' - else: - addBranch = branch2Name - otherBranch = branch1Name - mode = bMode - sha = bSha - conf = 'directory/file' - - if path in currentDirectorySet: - cleanMerge = False - newPath = uniquePath(path, addBranch) - output('CONFLICT (' + conf + '):', - 'There is a directory with name', path, 'in', - otherBranch + '. Adding', path, 'as', newPath) - - removeFile(False, path) - updateFile(False, sha, mode, newPath) - else: - output('Adding', path) - updateFile(True, sha, mode, path) - - elif not oSha and aSha and bSha: - # - # Case C: Added in both (check for same permissions). - # - if aSha == bSha: - if aMode != bMode: - cleanMerge = False - output('CONFLICT: File', path, - 'added identically in both branches, but permissions', - 'conflict', '0%o' % aMode, '->', '0%o' % bMode) - output('CONFLICT: adding with permission:', '0%o' % aMode) - - updateFile(False, aSha, aMode, path) - else: - # This case is handled by git-read-tree - assert(False) - else: - cleanMerge = False - newPath1 = uniquePath(path, branch1Name) - newPath2 = uniquePath(path, branch2Name) - output('CONFLICT (add/add): File', path, - 'added non-identically in both branches. Adding as', - newPath1, 'and', newPath2, 'instead.') - removeFile(False, path) - updateFile(False, aSha, aMode, newPath1) - updateFile(False, bSha, bMode, newPath2) - - elif oSha and aSha and bSha: - # - # case D: Modified in both, but differently. - # - output('Auto-merging', path) - [sha, mode, clean, dummy] = \ - mergeFile(path, oSha, oMode, - path, aSha, aMode, - path, bSha, bMode, - branch1Name, branch2Name) - if clean: - updateFile(True, sha, mode, path) - else: - cleanMerge = False - output('CONFLICT (content): Merge conflict in', path) - - if cacheOnly: - updateFile(False, sha, mode, path) - else: - updateFileExt(sha, mode, path, updateCache=False, updateWd=True) - else: - die("ERROR: Fatal merge failure, shouldn't happen.") - - return cleanMerge - -def usage(): - die('Usage:', sys.argv[0], ' ... -- ..') - -# main entry point as merge strategy module -# The first parameters up to -- are merge bases, and the rest are heads. - -if len(sys.argv) < 4: - usage() - -bases = [] -for nextArg in xrange(1, len(sys.argv)): - if sys.argv[nextArg] == '--': - if len(sys.argv) != nextArg + 3: - die('Not handling anything other than two heads merge.') - try: - h1 = firstBranch = sys.argv[nextArg + 1] - h2 = secondBranch = sys.argv[nextArg + 2] - except IndexError: - usage() - break - else: - bases.append(sys.argv[nextArg]) - -print 'Merging', h1, 'with', h2 - -try: - h1 = runProgram(['git-rev-parse', '--verify', h1 + '^0']).rstrip() - h2 = runProgram(['git-rev-parse', '--verify', h2 + '^0']).rstrip() - - if len(bases) == 1: - base = runProgram(['git-rev-parse', '--verify', - bases[0] + '^0']).rstrip() - ancestor = Commit(base, None) - [dummy, clean] = merge(Commit(h1, None), Commit(h2, None), - firstBranch, secondBranch, None, 0, - ancestor) - else: - graph = buildGraph([h1, h2]) - [dummy, clean] = merge(graph.shaMap[h1], graph.shaMap[h2], - firstBranch, secondBranch, graph) - - print '' -except: - if isinstance(sys.exc_info()[1], SystemExit): - raise - else: - traceback.print_exc(None, sys.stderr) - sys.exit(2) - -if clean: - sys.exit(0) -else: - sys.exit(1) diff --git a/git-merge.sh b/git-merge.sh index d049e16..5b34b4d 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -9,21 +9,15 @@ USAGE='[-n] [--no-commit] [--squash] [-s ]... < LF=' ' -all_strategies='recursive recur octopus resolve stupid ours' -case "${GIT_USE_RECUR_FOR_RECURSIVE}" in -'') - default_twohead_strategies=recursive ;; -?*) - default_twohead_strategies=recur ;; -esac +all_strategies='recur recursive recursive-old octopus resolve stupid ours' +default_twohead_strategies='recursive' default_octopus_strategies='octopus' no_trivial_merge_strategies='ours' use_strategies= index_merge=t if test "@@NO_PYTHON@@"; then - all_strategies='recur resolve octopus stupid ours' - default_twohead_strategies='resolve' + all_strategies='recur recursive resolve octopus stupid ours' fi dropsave() { @@ -122,10 +116,6 @@ do strategy="$2" shift ;; esac - case "$strategy,${GIT_USE_RECUR_FOR_RECURSIVE}" in - recursive,?*) - strategy=recur ;; - esac case " $all_strategies " in *" $strategy "*) use_strategies="$use_strategies$strategy " ;; diff --git a/git-rebase.sh b/git-rebase.sh index 20f74d4..a7373c0 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -35,13 +35,7 @@ If you would prefer to skip this patch, instead run \"git rebase --skip\". To restore the original branch and stop rebasing run \"git rebase --abort\". " unset newbase -case "${GIT_USE_RECUR_FOR_RECURSIVE}" in -'') - strategy=recursive ;; -?*) - strategy=recur ;; -esac - +strategy=recursive do_merge= dotest=$GIT_DIR/.dotest-merge prec=4 @@ -206,11 +200,6 @@ do shift done -case "$strategy,${GIT_USE_RECUR_FOR_RECURSIVE}" in -recursive,?*) - strategy=recur ;; -esac - # Make sure we do not have .dotest if test -z "$do_merge" then @@ -303,11 +292,11 @@ then exit $? fi -if test "@@NO_PYTHON@@" && test "$strategy" = "recursive" +if test "@@NO_PYTHON@@" && test "$strategy" = "recursive-old" then - die 'The recursive merge strategy currently relies on Python, + die 'The recursive-old merge strategy is written in Python, which this installation of git was not configured with. Please consider -a different merge strategy (e.g. octopus, resolve, stupid, ours) +a different merge strategy (e.g. recursive, resolve, or stupid) or install Python and git with Python support.' fi diff --git a/t/test-lib.sh b/t/test-lib.sh index e75ad5f..0fe2718 100755 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -211,7 +211,7 @@ export PATH GIT_EXEC_PATH PYTHON=`sed -e '1{ s/^#!// q -}' ../git-merge-recursive` || { +}' ../git-merge-recursive-old` || { error "You haven't built things yet, have you?" } "$PYTHON" -c 'import subprocess' 2>/dev/null || { -- cgit v0.10.2-6-g49f6 From e8daf78a00fc618f4b8b5b6253580226560f7dec Mon Sep 17 00:00:00 2001 From: Franck Bui-Huu Date: Mon, 25 Sep 2006 12:31:52 +0200 Subject: git-archive: update documentation This patch documents zip backend options. It also adds git-archive command into the main git manual page. Signed-off-by: Franck Bui-Huu Signed-off-by: Junio C Hamano diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt index 913528d..031fcd5 100644 --- a/Documentation/git-archive.txt +++ b/Documentation/git-archive.txt @@ -40,6 +40,7 @@ OPTIONS :: This can be any options that the archiver backend understand. + See next section. --remote=:: Instead of making a tar archive from local repository, @@ -52,6 +53,18 @@ path:: If one or more paths are specified, include only these in the archive, otherwise include all files and subdirectories. +BACKEND EXTRA OPTIONS +--------------------- + +zip +~~~ +-0:: + Store the files instead of deflating them. +-9:: + Highest and slowest compression level. You can specify any + number from 1 to 9 to adjust compression speed and ratio. + + CONFIGURATION ------------- By default, file and directories modes are set to 0666 or 0777 in tar diff --git a/Documentation/git.txt b/Documentation/git.txt index 1bf5ef5..2135b65 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -266,6 +266,9 @@ gitlink:git-am[1]:: gitlink:git-applymbox[1]:: Apply patches from a mailbox, original version by Linus. +gitlink:git-archive[1]:: + Creates an archive of files from a named tree. + gitlink:git-bisect[1]:: Find the change that introduced a bug by binary search. -- cgit v0.10.2-6-g49f6 From 4dafd7d2444051fe0e1eb17e9ded503ad7d9dd25 Mon Sep 17 00:00:00 2001 From: Alex Riesen Date: Mon, 25 Sep 2006 23:19:00 +0200 Subject: Use const for interpolate arguments Signed-off-by: Alex Riesen Signed-off-by: Junio C Hamano diff --git a/interpolate.c b/interpolate.c index d82f1b5..4570c12 100644 --- a/interpolate.c +++ b/interpolate.c @@ -25,10 +25,10 @@ */ int interpolate(char *result, int reslen, - char *orig, - struct interp *interps, int ninterps) + const char *orig, + const struct interp *interps, int ninterps) { - char *src = orig; + const char *src = orig; char *dest = result; int newlen = 0; char *name, *value; diff --git a/interpolate.h b/interpolate.h index 00c63a5..d16f924 100644 --- a/interpolate.h +++ b/interpolate.h @@ -5,6 +5,11 @@ #ifndef INTERPOLATE_H #define INTERPOLATE_H +/* + * Convert a NUL-terminated string in buffer orig, + * performing substitutions on %-named sub-strings from + * the interpretation table. + */ struct interp { char *name; @@ -12,7 +17,7 @@ struct interp { }; extern int interpolate(char *result, int reslen, - char *orig, - struct interp *interps, int ninterps); + const char *orig, + const struct interp *interps, int ninterps); #endif /* INTERPOLATE_H */ -- cgit v0.10.2-6-g49f6 From 9c7b0b3fc46e552bde1a65cd1950c3634854332b Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Tue, 26 Sep 2006 07:23:37 +0200 Subject: Remove empty ref directories that prevent creating a ref. This patch also adds test cases from Linus and Junio. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano diff --git a/git-branch.sh b/git-branch.sh index e0501ec..4f31903 100755 --- a/git-branch.sh +++ b/git-branch.sh @@ -112,6 +112,16 @@ rev=$(git-rev-parse --verify "$head") || exit git-check-ref-format "heads/$branchname" || die "we do not like '$branchname' as a branch name." +if [ -d "$GIT_DIR/refs/heads/$branchname" ] +then + for refdir in `cd "$GIT_DIR" && \ + find "refs/heads/$branchname" -type d | sort -r` + do + rmdir "$GIT_DIR/$refdir" || \ + die "Could not delete '$refdir', there may still be a ref there." + done +fi + if [ -e "$GIT_DIR/refs/heads/$branchname" ] then if test '' = "$force" diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 5b04efc..6907cbc 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -61,4 +61,16 @@ test_expect_success \ test -f .git/logs/refs/heads/g/h/i && diff expect .git/logs/refs/heads/g/h/i' +test_expect_success \ + 'git branch j/k should work after branch j has been deleted' \ + 'git-branch j && + git-branch -d j && + git-branch j/k' + +test_expect_success \ + 'git branch l should work after branch l/m has been deleted' \ + 'git-branch l/m && + git-branch -d l/m && + git-branch l' + test_done -- cgit v0.10.2-6-g49f6 From fd28b34afd9bbd58297a25edced3f504c9a5487a Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Wed, 27 Sep 2006 01:21:19 -0400 Subject: Ignore executable bit when adding files if filemode=0. If the user has configured core.filemode=0 then we shouldn't set the execute bit in the index when adding a new file as the user has indicated that the local filesystem can't be trusted. This means that when adding files that should be marked executable in a repository with core.filemode=0 the user must perform a 'git update-index --chmod=+x' on the file before committing the addition. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/builtin-update-index.c b/builtin-update-index.c index 0620e77..a3c0a45 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -112,11 +112,13 @@ static int add_file_to_cache(const char *path) ce->ce_mode = create_ce_mode(st.st_mode); if (!trust_executable_bit) { /* If there is an existing entry, pick the mode bits - * from it. + * from it, otherwise force to 644. */ int pos = cache_name_pos(path, namelen); if (0 <= pos) ce->ce_mode = active_cache[pos]->ce_mode; + else + ce->ce_mode = create_ce_mode(S_IFREG | 0644); } if (index_path(ce->sha1, path, &st, !info_only)) diff --git a/read-cache.c b/read-cache.c index 20c9d49..97c3867 100644 --- a/read-cache.c +++ b/read-cache.c @@ -347,11 +347,13 @@ int add_file_to_index(const char *path, int verbose) ce->ce_mode = create_ce_mode(st.st_mode); if (!trust_executable_bit) { /* If there is an existing entry, pick the mode bits - * from it. + * from it, otherwise force to 644. */ int pos = cache_name_pos(path, namelen); if (pos >= 0) ce->ce_mode = active_cache[pos]->ce_mode; + else + ce->ce_mode = create_ce_mode(S_IFREG | 0644); } if (index_path(ce->sha1, path, &st, 1)) diff --git a/t/t3700-add.sh b/t/t3700-add.sh index 6cd05c3..d36f22d 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -19,4 +19,26 @@ test_expect_success \ 'Test that "git-add -- -q" works' \ 'touch -- -q && git-add -- -q' +test_expect_success \ + 'git-add: Test that executable bit is not used if core.filemode=0' \ + 'git repo-config core.filemode 0 && + echo foo >xfoo1 && + chmod 755 xfoo1 && + git-add xfoo1 && + case "`git-ls-files --stage xfoo1`" in + 100644" "*xfoo1) echo ok;; + *) echo fail; git-ls-files --stage xfoo1; exit 1;; + esac' + +test_expect_success \ + 'git-update-index --add: Test that executable bit is not used...' \ + 'git repo-config core.filemode 0 && + echo foo >xfoo2 && + chmod 755 xfoo2 && + git-add xfoo2 && + case "`git-ls-files --stage xfoo2`" in + 100644" "*xfoo2) echo ok;; + *) echo fail; git-ls-files --stage xfoo2; exit 1;; + esac' + test_done -- cgit v0.10.2-6-g49f6 From 191414c025c3f0032d9b4843d629e4877126a748 Mon Sep 17 00:00:00 2001 From: Sergey Vlasov Date: Tue, 26 Sep 2006 13:42:55 +0400 Subject: git-svn: Fix fetch --no-ignore-externals with GIT_SVN_NO_LIB=1 When using Subversion 1.3.1 without Perl bindings (GIT_SVN_NO_LIB=1), "git-svn fetch --no-ignore-externals" fails with errors like: Tree (.../.git/svn/git-svn/tree) is not clean: X directory_with_external In this case the 'X' lines in the "svn status" output are not a sign of unclean tree, and therefore should be ignored. Signed-off-by: Sergey Vlasov Acked-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index 017f45a..f5c7d46 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1247,6 +1247,7 @@ sub assert_svn_wc_clean { } my @status = grep(!/^Performing status on external/,(`svn status`)); @status = grep(!/^\s*$/,@status); + @status = grep(!/^X/,@status) if $_no_ignore_ext; if (scalar @status) { print STDERR "Tree ($SVN_WC) is not clean:\n"; print STDERR $_ foreach @status; -- cgit v0.10.2-6-g49f6 From 4a0641b7cf833644b286b56bb57d66b5538e4418 Mon Sep 17 00:00:00 2001 From: Yasushi SHOJI Date: Wed, 27 Sep 2006 12:04:10 +0900 Subject: gitweb: Decode long title for link tooltips This is a simple one liner to decode long title string in perl's internal form to utf-8 for link tooltips. This is not crucial if the commit message is all in ASCII, however, if you decide to use other encoding, such as UTF-8, tooltips ain't readable any more. Signed-off-by: Yasushi SHOJI Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 66be619..597d29f 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -617,7 +617,7 @@ sub format_subject_html { if (length($short) < length($long)) { return $cgi->a({-href => $href, -class => "list subject", - -title => $long}, + -title => decode("utf8", $long, Encode::FB_DEFAULT)}, esc_html($short) . $extra); } else { return $cgi->a({-href => $href, -class => "list subject"}, -- cgit v0.10.2-6-g49f6 From 5a03e7f25334a6bf1dbbfdb9830d41de5b8f0d7f Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Mon, 25 Sep 2006 01:24:38 -0400 Subject: Allow git-checkout when on a non-existant branch. I've seen some users get into situtations where their HEAD symbolic-ref is pointing at a non-existant ref. (Sometimes this happens during clone when the remote repository lacks a 'master' branch.) If this happens the user is unable to use git-checkout to switch branches as there is no prior commit to merge from. So instead of giving the user low-level errors about how HEAD can't be resolved and how not a single revision was given change the type of checkout to be a force and go through with the user's request anyway. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/git-checkout.sh b/git-checkout.sh index 580a9e8..dd47724 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -4,8 +4,8 @@ USAGE='[-f] [-b ] [-m] [] [...]' SUBDIRECTORY_OK=Sometimes . git-sh-setup -old=$(git-rev-parse HEAD) old_name=HEAD +old=$(git-rev-parse --verify $old_name 2>/dev/null) new= new_name= force= @@ -139,6 +139,13 @@ fi die "git checkout: to checkout the requested commit you need to specify a name for a new branch which is created and switched to" +if [ "X$old" = X ] +then + echo "warning: You do not appear to currently be on a branch." >&2 + echo "warning: Forcing checkout of $new_name." >&2 + force=1 +fi + if [ "$force" ] then git-read-tree --reset -u $new diff --git a/t/t7201-co.sh b/t/t7201-co.sh index b64e8b7..085d4a0 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -31,6 +31,15 @@ test_expect_success setup ' git checkout master ' +test_expect_success "checkout from non-existing branch" ' + + git checkout -b delete-me master && + rm .git/refs/heads/delete-me && + test refs/heads/delete-me = "$(git symbolic-ref HEAD)" && + git checkout master && + test refs/heads/master = "$(git symbolic-ref HEAD)" +' + test_expect_success "checkout with dirty tree without -m" ' fill 0 1 2 3 4 5 >one && -- cgit v0.10.2-6-g49f6 From 16652170bf80542fd77de75fb88da2f7761f65c4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 27 Sep 2006 12:34:37 -0700 Subject: An illustration of rev-list --parents --pretty=raw This script creates two separate histories, A and B, each of which does: (A0, B0): create fileA and subdir/fileB (A1, B1): modify fileA (A2, B2): modify subdir/fileB and then grafts them together to make B0 a child of A2. So the final history looks like (time flows from top to bottom): true parent touches subdir? A0 none yes (creates it) A1 A0 no A2 A1 yes B0 none yes (different from what's in A2) B1 B0 no B2 B1 yes "git rev-list --parents --pretty=raw B2" would give "fake" parents on the "commit " header lines while "parent " header lines show the parent as recorded in the commit object (i.e. B0 appears to have A2 as its parent on "commit " header but there is no "parent A2" header line in it). When you have path limiters, we simplify history to omit commits that do not affect the specified paths. So "git rev-list --parents --pretty=raw B2 subdir" would return "B2 B0 A2 A0" (because B1 and A1 do not touch the path). When it does so, the "commit " header lines have "fake" parents (i.e. B2 appears to have B0 as its parent on "commit " header), but you can still get the true parents by looking at "parent " header. Signed-off-by: Junio C Hamano diff --git a/t/t6001-rev-list-graft.sh b/t/t6001-rev-list-graft.sh new file mode 100755 index 0000000..b2131cd --- /dev/null +++ b/t/t6001-rev-list-graft.sh @@ -0,0 +1,113 @@ +#!/bin/sh + +test_description='Revision traversal vs grafts and path limiter' + +. ./test-lib.sh + +test_expect_success setup ' + mkdir subdir && + echo >fileA fileA && + echo >subdir/fileB fileB && + git add fileA subdir/fileB && + git commit -a -m "Initial in one history." && + A0=`git rev-parse --verify HEAD` && + + echo >fileA fileA modified && + git commit -a -m "Second in one history." && + A1=`git rev-parse --verify HEAD` && + + echo >subdir/fileB fileB modified && + git commit -a -m "Third in one history." && + A2=`git rev-parse --verify HEAD` && + + rm -f .git/refs/heads/master .git/index && + + echo >fileA fileA again && + echo >subdir/fileB fileB again && + git add fileA subdir/fileB && + git commit -a -m "Initial in alternate history." && + B0=`git rev-parse --verify HEAD` && + + echo >fileA fileA modified in alternate history && + git commit -a -m "Second in alternate history." && + B1=`git rev-parse --verify HEAD` && + + echo >subdir/fileB fileB modified in alternate history && + git commit -a -m "Third in alternate history." && + B2=`git rev-parse --verify HEAD` && + : done +' + +check () { + type=$1 + shift + + arg= + which=arg + rm -f test.expect + for a + do + if test "z$a" = z-- + then + which=expect + child= + continue + fi + if test "$which" = arg + then + arg="$arg$a " + continue + fi + if test "$type" = basic + then + echo "$a" + else + if test "z$child" != z + then + echo "$child $a" + fi + child="$a" + fi + done >test.expect + if test "$type" != basic && test "z$child" != z + then + echo >>test.expect $child + fi + if test $type = basic + then + git rev-list $arg >test.actual + elif test $type = parents + then + git rev-list --parents $arg >test.actual + elif test $type = parents-raw + then + git rev-list --parents --pretty=raw $arg | + sed -n -e 's/^commit //p' >test.actual + fi + diff test.expect test.actual +} + +for type in basic parents parents-raw +do + test_expect_success 'without grafts' " + rm -f .git/info/grafts + check $type $B2 -- $B2 $B1 $B0 + " + + test_expect_success 'with grafts' " + echo '$B0 $A2' >.git/info/grafts + check $type $B2 -- $B2 $B1 $B0 $A2 $A1 $A0 + " + + test_expect_success 'without grafts, with pathlimit' " + rm -f .git/info/grafts + check $type $B2 subdir -- $B2 $B0 + " + + test_expect_success 'with grafts, with pathlimit' " + echo '$B0 $A2' >.git/info/grafts + check $type $B2 subdir -- $B2 $B0 $A2 $A0 + " + +done +test_done -- cgit v0.10.2-6-g49f6 From 209e7569313aa045da6d55e333c884e49e7d8fb2 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Wed, 27 Sep 2006 11:18:49 -0400 Subject: Corrected copy-and-paste thinko in ignore executable bit test case. This test should be testing update-index --add, not git-add as the latter is implemented in terms of the former. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/t/t3700-add.sh b/t/t3700-add.sh index d36f22d..c20e4c2 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -35,7 +35,7 @@ test_expect_success \ 'git repo-config core.filemode 0 && echo foo >xfoo2 && chmod 755 xfoo2 && - git-add xfoo2 && + git-update-index --add xfoo2 && case "`git-ls-files --stage xfoo2`" in 100644" "*xfoo2) echo ok;; *) echo fail; git-ls-files --stage xfoo2; exit 1;; -- cgit v0.10.2-6-g49f6 From b48fb5b6a950a6757b790e9160967065a3e03978 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 27 Sep 2006 16:27:10 -0700 Subject: grep: free expressions and patterns when done. Signed-off-by: Junio C Hamano diff --git a/builtin-grep.c b/builtin-grep.c index 6718788..4205e5d 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -325,6 +325,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached) else hit |= grep_file(opt, ce->name); } + free_grep_patterns(opt); return hit; } @@ -694,5 +695,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (grep_object(&opt, paths, real_obj, list.objects[i].name)) hit = 1; } + free_grep_patterns(&opt); return !hit; } diff --git a/grep.c b/grep.c index cc8d684..2c740bd 100644 --- a/grep.c +++ b/grep.c @@ -167,6 +167,46 @@ void compile_grep_patterns(struct grep_opt *opt) die("incomplete pattern expression: %s", p->pattern); } +static void free_pattern_expr(struct grep_expr *x) +{ + switch (x->node) { + case GREP_NODE_ATOM: + break; + case GREP_NODE_NOT: + free_pattern_expr(x->u.unary); + break; + case GREP_NODE_AND: + case GREP_NODE_OR: + free_pattern_expr(x->u.binary.left); + free_pattern_expr(x->u.binary.right); + break; + } + free(x); +} + +void free_grep_patterns(struct grep_opt *opt) +{ + struct grep_pat *p, *n; + + for (p = opt->pattern_list; p; p = n) { + n = p->next; + switch (p->token) { + case GREP_PATTERN: /* atom */ + case GREP_PATTERN_HEAD: + case GREP_PATTERN_BODY: + regfree(&p->regexp); + break; + default: + break; + } + free(p); + } + + if (!opt->extended) + return; + free_pattern_expr(opt->pattern_expression); +} + static char *end_of_line(char *cp, unsigned long *left) { unsigned long l = *left; @@ -439,6 +479,8 @@ int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long lno++; } + free(prev); + if (opt->status_only) return 0; if (opt->unmatch_name_only) { diff --git a/grep.h b/grep.h index 0b503ea..af9098c 100644 --- a/grep.h +++ b/grep.h @@ -73,6 +73,7 @@ struct grep_opt { extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t); extern void compile_grep_patterns(struct grep_opt *opt); +extern void free_grep_patterns(struct grep_opt *opt); extern int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size); #endif -- cgit v0.10.2-6-g49f6 From a3f5d02edb2c1a037ed3ed8d2ebd3f3e5da9d198 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 27 Sep 2006 16:42:53 -0700 Subject: grep: fix --fixed-strings combined with expression. "git grep --fixed-strings -e GIT --and -e VERSION .gitignore" misbehaved because we did not notice this needs to grab lines that have the given two fixed strings at the same time. Signed-off-by: Junio C Hamano diff --git a/grep.c b/grep.c index 2c740bd..c411ddd 100644 --- a/grep.c +++ b/grep.c @@ -138,16 +138,13 @@ void compile_grep_patterns(struct grep_opt *opt) { struct grep_pat *p; - if (opt->fixed) - return; - - /* First compile regexps */ for (p = opt->pattern_list; p; p = p->next) { switch (p->token) { case GREP_PATTERN: /* atom */ case GREP_PATTERN_HEAD: case GREP_PATTERN_BODY: - compile_regexp(p, opt); + if (!opt->fixed) + compile_regexp(p, opt); break; default: opt->extended = 1; -- cgit v0.10.2-6-g49f6 From 07ccbff89b6736a5253e6bba62c9a8e1f2da0ffd Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 28 Sep 2006 02:44:30 +0200 Subject: runstatus: do not recurse into subdirectories if not needed This speeds up the case when you run git-status, having an untracked subdirectory containing huge amounts of files. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/dir.c b/dir.c index e2f472b..96389b3 100644 --- a/dir.c +++ b/dir.c @@ -283,7 +283,7 @@ static int dir_exists(const char *dirname, int len) * Also, we ignore the name ".git" (even if it is not a directory). * That likely will not change. */ -static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen) +static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen, int check_only) { DIR *fdir = opendir(path); int contents = 0; @@ -314,7 +314,6 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co switch (DTYPE(de)) { struct stat st; - int subdir, rewind_base; default: continue; case DT_UNKNOWN: @@ -328,26 +327,30 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co case DT_DIR: memcpy(fullname + baselen + len, "/", 2); len++; - rewind_base = dir->nr; - subdir = read_directory_recursive(dir, fullname, fullname, - baselen + len); if (dir->show_other_directories && - (subdir || !dir->hide_empty_directories) && !dir_exists(fullname, baselen + len)) { - /* Rewind the read subdirectory */ - while (dir->nr > rewind_base) - free(dir->entries[--dir->nr]); + if (dir->hide_empty_directories && + !read_directory_recursive(dir, + fullname, fullname, + baselen + len, 1)) + continue; break; } - contents += subdir; + + contents += read_directory_recursive(dir, + fullname, fullname, baselen + len, 0); continue; case DT_REG: case DT_LNK: break; } - add_name(dir, fullname, baselen + len); contents++; + if (check_only) + goto exit_early; + else + add_name(dir, fullname, baselen + len); } +exit_early: closedir(fdir); pop_exclude_per_directory(dir, exclude_stk); @@ -393,7 +396,7 @@ int read_directory(struct dir_struct *dir, const char *path, const char *base, i } } - read_directory_recursive(dir, path, base, baselen); + read_directory_recursive(dir, path, base, baselen, 0); qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name); return dir->nr; } -- cgit v0.10.2-6-g49f6
" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary")}, "summary") . " | " . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"shortlog")}, "shortlog") . " | " . - $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"log")}, "log") . " | " . + $cgi->a({-href => href(project=>$pr->{'path'}, action=>"tree")}, "tree") . "
" . $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " . - $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff"); - if ($have_snapshot) { - print " | " . $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot"); - } + $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " . + $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree"); print "
" . $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'})}, "shortlog") . " | " . - $cgi->a({-href => href(action=>"log", hash=>$tag{'name'})}, "log") . + $cgi->a({-href => href(action=>"log", hash=>$tag{'name'})}, "log") . " | " . + $cgi->a({-href => href(action=>"tree", hash=>$tag{'name'}, hash_base=>$tag{'name'})}, "tree") . "