diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt index 0ee887d73c..5c9888d014 100644 --- a/Documentation/git-blame.txt +++ b/Documentation/git-blame.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git-blame' [-c] [-l] [-t] [-f] [-n] [-p] [--incremental] [-L n,m] [-S ] - [-M] [-C] [-C] [--since=] [] [--] + [-M] [-C] [-C] [--since=] [ | --contents ] [--] DESCRIPTION ----------- @@ -67,6 +67,13 @@ OPTIONS Show the result incrementally in a format designed for machine consumption. +--contents :: + When is not specified, the command annotates the + changes starting backwards from the working tree copy. + This flag makes the command pretend as if the working + tree copy has the contents of he named file (specify + `-` to make the command read from the standard input). + -M:: Detect moving lines in the file as well. When a commit moves a block of lines in a file (e.g. the original file diff --git a/Documentation/git-cvsexportcommit.txt b/Documentation/git-cvsexportcommit.txt index 347cbcec35..27d531b888 100644 --- a/Documentation/git-cvsexportcommit.txt +++ b/Documentation/git-cvsexportcommit.txt @@ -8,7 +8,7 @@ git-cvsexportcommit - Export a single commit to a CVS checkout SYNOPSIS -------- -'git-cvsexportcommit' [-h] [-v] [-c] [-p] [-a] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID +'git-cvsexportcommit' [-h] [-v] [-c] [-P] [-p] [-a] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID DESCRIPTION @@ -46,6 +46,9 @@ OPTIONS -f:: Force the merge even if the files are not up to date. +-P:: + Force the parent commit, even if it is not a direct parent. + -m:: Prepend the commit message with the provided prefix. Useful for patch series and the like. diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index da52eba7b3..f49b0d944c 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -66,7 +66,7 @@ keys. For all objects, the following names can be used: refname:: - The name of the ref (the part after $GIT_DIR/refs/). + The name of the ref (the part after $GIT_DIR/). objecttype:: The type of the object (`blob`, `tree`, `commit`, `tag`). diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 0cb9e1f10a..977f661b9d 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -114,6 +114,27 @@ would result in: This is useful when topicB does not depend on topicA. +A range of commits could also be removed with rebase. If we have +the following situation: + +------------ + E---F---G---H---I---J topicA +------------ + +then the command + + git-rebase --onto topicA~5 topicA~2 topicA + +would result in the removal of commits F and G: + +------------ + E---H'---I'---J' topicA +------------ + +This is useful if F and G were flawed in some way, or should not be +part of topicA. Note that the argument to --onto and the +parameter can be any valid commit-ish. + In case of conflict, git-rebase will stop at the first problematic commit and leave conflict markers in the tree. You can use git diff to locate the markers (<<<<<<) and make edits to resolve the conflict. For each @@ -141,10 +162,12 @@ OPTIONS :: Starting point at which to create the new commits. If the --onto option is not specified, the starting point is - . + . May be any valid commit, and not just an + existing branch name. :: - Upstream branch to compare against. + Upstream branch to compare against. May be any valid commit, + not just an existing branch name. :: Working branch; defaults to HEAD. diff --git a/Documentation/git-tools.txt b/Documentation/git-tools.txt index 6b407f9adf..10653ff898 100644 --- a/Documentation/git-tools.txt +++ b/Documentation/git-tools.txt @@ -86,6 +86,14 @@ Foreign SCM interface series in git back and forth. + - *hg-to-git* (contrib/) + + hg-to-git converts a Mercurial repository into a git one, and + preserves the full branch history in the process. hg-to-git can + also be used in an incremental way to keep the git repository + in sync with the master Mercurial repository. + + Others ------ diff --git a/builtin-blame.c b/builtin-blame.c index 1c21204a2c..a18ef81a14 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -15,9 +15,10 @@ #include "revision.h" #include "quote.h" #include "xdiff-interface.h" +#include "cache-tree.h" static char blame_usage[] = -"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S ] [-M] [-C] [-C] [commit] [--] file\n" +"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S ] [-M] [-C] [-C] [--contents ] [--incremental] [commit] [--] file\n" " -c, --compatibility Use the same output mode as git-annotate (Default: off)\n" " -b Show blank SHA-1 for boundary commits (Default: off)\n" " -l, --long Show long commit SHA1 (Default: off)\n" @@ -29,6 +30,7 @@ static char blame_usage[] = " -L n,m Process only line range n,m, counting from 1\n" " -M, -C Find line movements within and across files\n" " --incremental Show blame entries as we find them, incrementally\n" +" --contents file Use 's contents as the final image\n" " -S revs-file Use revisions from revs-file instead of calling git-rev-list\n"; static int longest_file; @@ -333,9 +335,13 @@ static struct origin *find_origin(struct scoreboard *sb, diff_tree_setup_paths(paths, &diff_opts); if (diff_setup_done(&diff_opts) < 0) die("diff-setup"); - diff_tree_sha1(parent->tree->object.sha1, - origin->commit->tree->object.sha1, - "", &diff_opts); + + if (is_null_sha1(origin->commit->object.sha1)) + do_diff_cache(parent->tree->object.sha1, &diff_opts); + else + diff_tree_sha1(parent->tree->object.sha1, + origin->commit->tree->object.sha1, + "", &diff_opts); diffcore_std(&diff_opts); /* It is either one entry that says "modified", or "created", @@ -402,9 +408,13 @@ static struct origin *find_rename(struct scoreboard *sb, diff_tree_setup_paths(paths, &diff_opts); if (diff_setup_done(&diff_opts) < 0) die("diff-setup"); - diff_tree_sha1(parent->tree->object.sha1, - origin->commit->tree->object.sha1, - "", &diff_opts); + + if (is_null_sha1(origin->commit->object.sha1)) + do_diff_cache(parent->tree->object.sha1, &diff_opts); + else + diff_tree_sha1(parent->tree->object.sha1, + origin->commit->tree->object.sha1, + "", &diff_opts); diffcore_std(&diff_opts); for (i = 0; i < diff_queued_diff.nr; i++) { @@ -1047,9 +1057,12 @@ static int find_copy_in_parent(struct scoreboard *sb, (!porigin || strcmp(target->path, porigin->path))) diff_opts.find_copies_harder = 1; - diff_tree_sha1(parent->tree->object.sha1, - target->commit->tree->object.sha1, - "", &diff_opts); + if (is_null_sha1(target->commit->object.sha1)) + do_diff_cache(parent->tree->object.sha1, &diff_opts); + else + diff_tree_sha1(parent->tree->object.sha1, + target->commit->tree->object.sha1, + "", &diff_opts); if (!diff_opts.find_copies_harder) diffcore_std(&diff_opts); @@ -1336,9 +1349,9 @@ static void get_commit_info(struct commit *commit, tmp += 2; endp = strchr(tmp, '\n'); if (!endp) - goto error_out; + endp = tmp + strlen(tmp); len = endp - tmp; - if (len >= sizeof(summary_buf)) + if (len >= sizeof(summary_buf) || len == 0) goto error_out; memcpy(summary_buf, tmp, len); summary_buf[len] = 0; @@ -1910,6 +1923,137 @@ static int git_blame_config(const char *var, const char *value) return git_default_config(var, value); } +static struct commit *fake_working_tree_commit(const char *path, const char *contents_from) +{ + struct commit *commit; + struct origin *origin; + unsigned char head_sha1[20]; + char *buf; + const char *ident; + int fd; + time_t now; + unsigned long fin_size; + int size, len; + struct cache_entry *ce; + unsigned mode; + + if (get_sha1("HEAD", head_sha1)) + die("No such ref: HEAD"); + + time(&now); + commit = xcalloc(1, sizeof(*commit)); + commit->parents = xcalloc(1, sizeof(*commit->parents)); + commit->parents->item = lookup_commit_reference(head_sha1); + commit->object.parsed = 1; + commit->date = now; + commit->object.type = OBJ_COMMIT; + + origin = make_origin(commit, path); + + if (!contents_from || strcmp("-", contents_from)) { + struct stat st; + const char *read_from; + + if (contents_from) { + if (stat(contents_from, &st) < 0) + die("Cannot stat %s", contents_from); + read_from = contents_from; + } + else { + if (lstat(path, &st) < 0) + die("Cannot lstat %s", path); + read_from = path; + } + fin_size = st.st_size; + buf = xmalloc(fin_size+1); + mode = canon_mode(st.st_mode); + switch (st.st_mode & S_IFMT) { + case S_IFREG: + fd = open(read_from, O_RDONLY); + if (fd < 0) + die("cannot open %s", read_from); + if (read_in_full(fd, buf, fin_size) != fin_size) + die("cannot read %s", read_from); + break; + case S_IFLNK: + if (readlink(read_from, buf, fin_size+1) != fin_size) + die("cannot readlink %s", read_from); + break; + default: + die("unsupported file type %s", read_from); + } + } + else { + /* Reading from stdin */ + contents_from = "standard input"; + buf = NULL; + fin_size = 0; + mode = 0; + while (1) { + ssize_t cnt = 8192; + buf = xrealloc(buf, fin_size + cnt); + cnt = xread(0, buf + fin_size, cnt); + if (cnt < 0) + die("read error %s from stdin", + strerror(errno)); + if (!cnt) + break; + fin_size += cnt; + } + buf = xrealloc(buf, fin_size + 1); + } + buf[fin_size] = 0; + origin->file.ptr = buf; + origin->file.size = fin_size; + pretend_sha1_file(buf, fin_size, blob_type, origin->blob_sha1); + commit->util = origin; + + /* + * Read the current index, replace the path entry with + * origin->blob_sha1 without mucking with its mode or type + * bits; we are not going to write this index out -- we just + * want to run "diff-index --cached". + */ + discard_cache(); + read_cache(); + + len = strlen(path); + if (!mode) { + int pos = cache_name_pos(path, len); + if (0 <= pos) + mode = ntohl(active_cache[pos]->ce_mode); + else + /* Let's not bother reading from HEAD tree */ + mode = S_IFREG | 0644; + } + size = cache_entry_size(len); + ce = xcalloc(1, size); + hashcpy(ce->sha1, origin->blob_sha1); + memcpy(ce->name, path, len); + ce->ce_flags = create_ce_flags(len, 0); + ce->ce_mode = create_ce_mode(mode); + add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); + + /* + * We are not going to write this out, so this does not matter + * right now, but someday we might optimize diff-index --cached + * with cache-tree information. + */ + cache_tree_invalidate_path(active_cache_tree, path); + + commit->buffer = xmalloc(400); + ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0); + sprintf(commit->buffer, + "tree 0000000000000000000000000000000000000000\n" + "parent %s\n" + "author %s\n" + "committer %s\n\n" + "Version of %s from %s\n", + sha1_to_hex(head_sha1), + ident, ident, path, contents_from ? contents_from : path); + return commit; +} + int cmd_blame(int argc, const char **argv, const char *prefix) { struct rev_info revs; @@ -1924,6 +2068,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) const char *final_commit_name = NULL; char type[10]; const char *bottomtop = NULL; + const char *contents_from = NULL; git_config(git_blame_config); save_commit_buffer = 0; @@ -1968,6 +2113,11 @@ int cmd_blame(int argc, const char **argv, const char *prefix) die("More than one '-L n,m' option given"); bottomtop = arg; } + else if (!strcmp("--contents", arg)) { + if (++i >= argc) + usage(blame_usage); + contents_from = argv[i]; + } else if (!strcmp("--incremental", arg)) incremental = 1; else if (!strcmp("--score-debug", arg)) @@ -2087,7 +2237,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) argv[unk] = NULL; init_revisions(&revs, NULL); - setup_revisions(unk, argv, &revs, "HEAD"); + setup_revisions(unk, argv, &revs, NULL); memset(&sb, 0, sizeof(sb)); /* @@ -2114,16 +2264,14 @@ int cmd_blame(int argc, const char **argv, const char *prefix) if (!sb.final) { /* * "--not A B -- path" without anything positive; - * default to HEAD. + * do not default to HEAD, but use the working tree + * or "--contents". */ - unsigned char head_sha1[20]; - - final_commit_name = "HEAD"; - if (get_sha1(final_commit_name, head_sha1)) - die("No such ref: HEAD"); - sb.final = lookup_commit_reference(head_sha1); - add_pending_object(&revs, &(sb.final->object), "HEAD"); + sb.final = fake_working_tree_commit(path, contents_from); + add_pending_object(&revs, &(sb.final->object), ":"); } + else if (contents_from) + die("Cannot use --contents with final commit object name"); /* * If we have bottom, this will mark the ancestors of the @@ -2132,11 +2280,22 @@ int cmd_blame(int argc, const char **argv, const char *prefix) */ prepare_revision_walk(&revs); - o = get_origin(&sb, sb.final, path); - if (fill_blob_sha1(o)) - die("no such path %s in %s", path, final_commit_name); + if (is_null_sha1(sb.final->object.sha1)) { + char *buf; + o = sb.final->util; + buf = xmalloc(o->file.size + 1); + memcpy(buf, o->file.ptr, o->file.size + 1); + sb.final_buf = buf; + sb.final_buf_size = o->file.size; + } + else { + o = get_origin(&sb, sb.final, path); + if (fill_blob_sha1(o)) + die("no such path %s in %s", path, final_commit_name); - sb.final_buf = read_sha1_file(o->blob_sha1, type, &sb.final_buf_size); + sb.final_buf = read_sha1_file(o->blob_sha1, type, + &sb.final_buf_size); + } num_read_blob++; lno = prepare_lines(&sb); diff --git a/builtin-ls-files.c b/builtin-ls-files.c index 21c2a6e2d9..ac89eb2f77 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -323,7 +323,7 @@ static const char ls_files_usage[] = int cmd_ls_files(int argc, const char **argv, const char *prefix) { int i; - int exc_given = 0; + int exc_given = 0, require_work_tree = 0; struct dir_struct dir; memset(&dir, 0, sizeof(dir)); @@ -363,14 +363,17 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) } if (!strcmp(arg, "-m") || !strcmp(arg, "--modified")) { show_modified = 1; + require_work_tree = 1; continue; } if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) { show_others = 1; + require_work_tree = 1; continue; } if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) { dir.show_ignored = 1; + require_work_tree = 1; continue; } if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) { @@ -379,6 +382,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) } if (!strcmp(arg, "-k") || !strcmp(arg, "--killed")) { show_killed = 1; + require_work_tree = 1; continue; } if (!strcmp(arg, "--directory")) { @@ -447,6 +451,10 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) break; } + if (require_work_tree && + (is_bare_repository() || is_inside_git_dir())) + die("This operation must be run in a work tree"); + pathspec = get_pathspec(prefix, argv + i); /* Verify that the pathspec matches the prefix */ diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index 3b716fba13..d53deaa369 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -347,6 +347,11 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) printf("%s/.git\n", cwd); continue; } + if (!strcmp(arg, "--is-inside-git-dir")) { + printf("%s\n", is_inside_git_dir() ? "true" + : "false"); + continue; + } if (!strncmp(arg, "--since=", 8)) { show_datestring("--max-age=", arg+8); continue; diff --git a/cache.h b/cache.h index 38a9bc02f6..87142571b3 100644 --- a/cache.h +++ b/cache.h @@ -257,6 +257,7 @@ extern void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, uns extern void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size); extern int hash_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *sha1); extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *return_sha1); +extern int pretend_sha1_file(void *, unsigned long, const char *, unsigned char *); extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type); diff --git a/contrib/blameview/blameview.perl b/contrib/blameview/blameview.perl index 5e9a67c123..807d01fe3d 100755 --- a/contrib/blameview/blameview.perl +++ b/contrib/blameview/blameview.perl @@ -3,7 +3,17 @@ use Gtk2 -init; use Gtk2::SimpleList; -my $fn = shift or die "require filename to blame"; +my $hash; +my $fn; +if ( @ARGV == 1 ) { + $hash = "HEAD"; + $fn = shift; +} elsif ( @ARGV == 2 ) { + $hash = shift; + $fn = shift; +} else { + die "Usage blameview [] "; +} Gtk2::Rc->parse_string(<<'EOS'); style "treeview_style" @@ -27,17 +37,24 @@ $scrolled_window->add($fileview); $fileview->get_column(0)->set_spacing(0); $fileview->set_size_request(1024, 768); $fileview->set_rules_hint(1); +$fileview->signal_connect (row_activated => sub { + my ($sl, $path, $column) = @_; + my $row_ref = $sl->get_row_data_from_path ($path); + system("blameview @$row_ref[0] $fn"); + # $row_ref is now an array ref to the double-clicked row's data. + }); my $fh; -open($fh, '-|', "git cat-file blob HEAD:$fn") +open($fh, '-|', "git cat-file blob $hash:$fn") or die "unable to open $fn: $!"; + while(<$fh>) { chomp; $fileview->{data}->[$.] = ['HEAD', '?', "$fn:$.", $_]; } my $blame; -open($blame, '-|', qw(git blame --incremental --), $fn) +open($blame, '-|', qw(git blame --incremental --), $fn, $hash) or die "cannot start git-blame $fn"; Glib::IO->add_watch(fileno($blame), 'in', \&read_blame_line); diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 466cc32f4c..430e6367a7 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -269,6 +269,7 @@ __git_commands () cvsimport) : import;; cvsserver) : daemon;; daemon) : daemon;; + diff-stages) : nobody uses it;; fsck-objects) : plumbing;; fetch-pack) : plumbing;; fmt-merge-msg) : plumbing;; @@ -296,6 +297,7 @@ __git_commands () reflog) : plumbing;; repo-config) : plumbing;; rerere) : plumbing;; + resolve) : dead dont use;; rev-list) : plumbing;; rev-parse) : plumbing;; runstatus) : plumbing;; @@ -405,6 +407,35 @@ _git_add () COMPREPLY=() } +_git_bisect () +{ + local i c=1 command + while [ $c -lt $COMP_CWORD ]; do + i="${COMP_WORDS[c]}" + case "$i" in + start|bad|good|reset|visualize|replay|log) + command="$i" + break + ;; + esac + c=$((++c)) + done + + if [ $c -eq $COMP_CWORD -a -z "$command" ]; then + __gitcomp "start bad good reset visualize replay log" + return + fi + + case "$command" in + bad|good|reset) + __gitcomp "$(__git_refs)" + ;; + *) + COMPREPLY=() + ;; + esac +} + _git_branch () { __gitcomp "$(__git_refs)" @@ -415,6 +446,11 @@ _git_checkout () __gitcomp "$(__git_refs)" } +_git_cherry () +{ + __gitcomp "$(__git_refs)" +} + _git_cherry_pick () { local cur="${COMP_WORDS[COMP_CWORD]}" @@ -502,6 +538,18 @@ _git_format_patch () __git_complete_revlist } +_git_gc () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + --*) + __gitcomp "--prune" + return + ;; + esac + COMPREPLY=() +} + _git_ls_remote () { __gitcomp "$(__git_remotes)" @@ -700,7 +748,7 @@ _git_config () __gitcomp " --global --list --replace-all --get --get-all --get-regexp - --unset --unset-all + --add --unset --unset-all " return ;; @@ -865,8 +913,10 @@ _git () am) _git_am ;; add) _git_add ;; apply) _git_apply ;; + bisect) _git_bisect ;; branch) _git_branch ;; checkout) _git_checkout ;; + cherry) _git_cherry ;; cherry-pick) _git_cherry_pick ;; commit) _git_commit ;; config) _git_config ;; @@ -874,6 +924,7 @@ _git () diff-tree) _git_diff_tree ;; fetch) _git_fetch ;; format-patch) _git_format_patch ;; + gc) _git_gc ;; log) _git_log ;; ls-remote) _git_ls_remote ;; ls-tree) _git_ls_tree ;; @@ -907,14 +958,17 @@ complete -o default -o nospace -F _git git complete -o default -o nospace -F _gitk gitk complete -o default -o nospace -F _git_am git-am complete -o default -o nospace -F _git_apply git-apply +complete -o default -o nospace -F _git_bisect git-bisect complete -o default -o nospace -F _git_branch git-branch complete -o default -o nospace -F _git_checkout git-checkout +complete -o default -o nospace -F _git_cherry git-cherry complete -o default -o nospace -F _git_cherry_pick git-cherry-pick complete -o default -o nospace -F _git_commit git-commit complete -o default -o nospace -F _git_diff git-diff complete -o default -o nospace -F _git_diff_tree git-diff-tree complete -o default -o nospace -F _git_fetch git-fetch complete -o default -o nospace -F _git_format_patch git-format-patch +complete -o default -o nospace -F _git_gc git-gc complete -o default -o nospace -F _git_log git-log complete -o default -o nospace -F _git_ls_remote git-ls-remote complete -o default -o nospace -F _git_ls_tree git-ls-tree @@ -939,6 +993,7 @@ complete -o default -o nospace -F _git_add git-add.exe complete -o default -o nospace -F _git_apply git-apply.exe complete -o default -o nospace -F _git git.exe complete -o default -o nospace -F _git_branch git-branch.exe +complete -o default -o nospace -F _git_cherry git-cherry.exe complete -o default -o nospace -F _git_diff git-diff.exe complete -o default -o nospace -F _git_diff_tree git-diff-tree.exe complete -o default -o nospace -F _git_format_patch git-format-patch.exe diff --git a/contrib/emacs/git-blame.el b/contrib/emacs/git-blame.el new file mode 100644 index 0000000000..62cf24c996 --- /dev/null +++ b/contrib/emacs/git-blame.el @@ -0,0 +1,180 @@ +;;; git-blame.el +;; David Kågedal +;; Message-ID: <87iren2vqx.fsf@morpheus.local> + +(require 'cl) +(defun color-scale (l) + (let* ((colors ()) + r g b) + (setq r l) + (while r + (setq g l) + (while g + (setq b l) + (while b + (push (concat "#" (car r) (car g) (car b)) colors) + (pop b)) + (pop g)) + (pop r)) + colors)) + +(defvar git-blame-dark-colors + (color-scale '("00" "04" "08" "0c" + "10" "14" "18" "1c" + "20" "24" "28" "2c" + "30" "34" "38" "3c"))) + +(defvar git-blame-light-colors + (color-scale '("c0" "c4" "c8" "cc" + "d0" "d4" "d8" "dc" + "e0" "e4" "e8" "ec" + "f0" "f4" "f8" "fc"))) + +(defvar git-blame-ancient-color "dark green") + +(defvar git-blame-overlays nil) +(defvar git-blame-cache nil) + +(defvar git-blame-mode nil) +(make-variable-buffer-local 'git-blame-mode) +(push (list 'git-blame-mode " blame") minor-mode-alist) + +(defun git-blame-mode (&optional arg) + (interactive "P") + (if arg + (setq git-blame-mode (eq arg 1)) + (setq git-blame-mode (not git-blame-mode))) + (make-local-variable 'git-blame-overlays) + (make-local-variable 'git-blame-colors) + (make-local-variable 'git-blame-cache) + (let ((bgmode (cdr (assoc 'background-mode (frame-parameters))))) + (if (eq bgmode 'dark) + (setq git-blame-colors git-blame-dark-colors) + (setq git-blame-colors git-blame-light-colors))) + (if git-blame-mode + (git-blame-run) + (git-blame-cleanup))) + +(defun git-blame-run () + (let* ((display-buf (current-buffer)) + (blame-buf (get-buffer-create + (concat " git blame for " (buffer-name)))) + (proc (start-process "git-blame" blame-buf + "git" "blame" "--incremental" + (file-name-nondirectory buffer-file-name)))) + (mapcar 'delete-overlay git-blame-overlays) + (setq git-blame-overlays nil) + (setq git-blame-cache (make-hash-table :test 'equal)) + (with-current-buffer blame-buf + (erase-buffer) + (make-local-variable 'git-blame-file) + (make-local-variable 'git-blame-current) + (setq git-blame-file display-buf) + (setq git-blame-current nil)) + (set-process-filter proc 'git-blame-filter) + (set-process-sentinel proc 'git-blame-sentinel))) + +(defun git-blame-cleanup () + "Remove all blame properties" + (mapcar 'delete-overlay git-blame-overlays) + (setq git-blame-overlays nil) + (let ((modified (buffer-modified-p))) + (remove-text-properties (point-min) (point-max) '(point-entered nil)) + (set-buffer-modified-p modified))) + +(defun git-blame-sentinel (proc status) + ;;(kill-buffer (process-buffer proc)) + (message "git blame finished")) + +(defvar in-blame-filter nil) + +(defun git-blame-filter (proc str) + (save-excursion + (set-buffer (process-buffer proc)) + (goto-char (process-mark proc)) + (insert-before-markers str) + (goto-char 0) + (unless in-blame-filter + (let ((more t) + (in-blame-filter t)) + (while more + (setq more (git-blame-parse))))))) + +(defun git-blame-parse () + (cond ((looking-at "\\([0-9a-f]\\{40\\}\\) \\([0-9]+\\) \\([0-9]+\\) \\([0-9]+\\)\n") + (let ((hash (match-string 1)) + (src-line (string-to-number (match-string 2))) + (res-line (string-to-number (match-string 3))) + (num-lines (string-to-number (match-string 4)))) + (setq git-blame-current + (git-blame-new-commit + hash src-line res-line num-lines))) + (delete-region (point) (match-end 0)) + t) + ((looking-at "filename \\(.+\\)\n") + (let ((filename (match-string 1))) + (git-blame-add-info "filename" filename)) + (delete-region (point) (match-end 0)) + t) + ((looking-at "\\([a-z-]+\\) \\(.+\\)\n") + (let ((key (match-string 1)) + (value (match-string 2))) + (git-blame-add-info key value)) + (delete-region (point) (match-end 0)) + t) + ((looking-at "boundary\n") + (setq git-blame-current nil) + (delete-region (point) (match-end 0)) + t) + (t + nil))) + + +(defun git-blame-new-commit (hash src-line res-line num-lines) + (save-excursion + (set-buffer git-blame-file) + (let ((info (gethash hash git-blame-cache)) + (inhibit-point-motion-hooks t)) + (when (not info) + (let ((color (pop git-blame-colors))) + (unless color + (setq color git-blame-ancient-color)) + (setq info (list hash src-line res-line num-lines + (cons 'color color)))) + (puthash hash info git-blame-cache)) + (goto-line res-line) + (while (> num-lines 0) + (if (get-text-property (point) 'git-blame) + (forward-line) + (let* ((start (point)) + (end (progn (forward-line 1) (point))) + (ovl (make-overlay start end))) + (push ovl git-blame-overlays) + (overlay-put ovl 'git-blame info) + (overlay-put ovl 'help-echo hash) + (overlay-put ovl 'face (list :background + (cdr (assq 'color (cddddr info))))) + ;;(overlay-put ovl 'point-entered + ;; `(lambda (x y) (git-blame-identify ,hash))) + (let ((modified (buffer-modified-p))) + (put-text-property (if (= start 1) start (1- start)) (1- end) + 'point-entered + `(lambda (x y) (git-blame-identify ,hash))) + (set-buffer-modified-p modified)))) + (setq num-lines (1- num-lines)))))) + +(defun git-blame-add-info (key value) + (if git-blame-current + (nconc git-blame-current (list (cons (intern key) value))))) + +(defun git-blame-current-commit () + (let ((info (get-char-property (point) 'git-blame))) + (if info + (car info) + (error "No commit info")))) + +(defun git-blame-identify (&optional hash) + (interactive) + (shell-command + (format "git log -1 --pretty=oneline %s" (or hash + (git-blame-current-commit))))) diff --git a/contrib/hg-to-git/hg-to-git.py b/contrib/hg-to-git/hg-to-git.py new file mode 100755 index 0000000000..37337ff01f --- /dev/null +++ b/contrib/hg-to-git/hg-to-git.py @@ -0,0 +1,233 @@ +#! /usr/bin/python + +""" hg-to-svn.py - A Mercurial to GIT converter + + Copyright (C)2007 Stelian Pop + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +import os, os.path, sys +import tempfile, popen2, pickle, getopt +import re + +# Maps hg version -> git version +hgvers = {} +# List of children for each hg revision +hgchildren = {} +# Current branch for each hg revision +hgbranch = {} + +#------------------------------------------------------------------------------ + +def usage(): + + print """\ +%s: [OPTIONS] + +options: + -s, --gitstate=FILE: name of the state to be saved/read + for incrementals + +required: + hgprj: name of the HG project to import (directory) +""" % sys.argv[0] + +#------------------------------------------------------------------------------ + +def getgitenv(user, date): + env = '' + elems = re.compile('(.*?)\s+<(.*)>').match(user) + if elems: + env += 'export GIT_AUTHOR_NAME="%s" ;' % elems.group(1) + env += 'export GIT_COMMITER_NAME="%s" ;' % elems.group(1) + env += 'export GIT_AUTHOR_EMAIL="%s" ;' % elems.group(2) + env += 'export GIT_COMMITER_EMAIL="%s" ;' % elems.group(2) + else: + env += 'export GIT_AUTHOR_NAME="%s" ;' % user + env += 'export GIT_COMMITER_NAME="%s" ;' % user + env += 'export GIT_AUTHOR_EMAIL= ;' + env += 'export GIT_COMMITER_EMAIL= ;' + + env += 'export GIT_AUTHOR_DATE="%s" ;' % date + env += 'export GIT_COMMITTER_DATE="%s" ;' % date + return env + +#------------------------------------------------------------------------------ + +state = '' + +try: + opts, args = getopt.getopt(sys.argv[1:], 's:t:', ['gitstate=', 'tempdir=']) + for o, a in opts: + if o in ('-s', '--gitstate'): + state = a + state = os.path.abspath(state) + + if len(args) != 1: + raise('params') +except: + usage() + sys.exit(1) + +hgprj = args[0] +os.chdir(hgprj) + +if state: + if os.path.exists(state): + print 'State does exist, reading' + f = open(state, 'r') + hgvers = pickle.load(f) + else: + print 'State does not exist, first run' + +tip = os.popen('hg tip | head -1 | cut -f 2 -d :').read().strip() +print 'tip is', tip + +# Calculate the branches +print 'analysing the branches...' +hgchildren["0"] = () +hgbranch["0"] = "master" +for cset in range(1, int(tip) + 1): + hgchildren[str(cset)] = () + prnts = os.popen('hg log -r %d | grep ^parent: | cut -f 2 -d :' % cset).readlines() + if len(prnts) > 0: + parent = prnts[0].strip() + else: + parent = str(cset - 1) + hgchildren[parent] += ( str(cset), ) + if len(prnts) > 1: + mparent = prnts[1].strip() + hgchildren[mparent] += ( str(cset), ) + else: + mparent = None + + if mparent: + # For merge changesets, take either one, preferably the 'master' branch + if hgbranch[mparent] == 'master': + hgbranch[str(cset)] = 'master' + else: + hgbranch[str(cset)] = hgbranch[parent] + else: + # Normal changesets + # For first children, take the parent branch, for the others create a new branch + if hgchildren[parent][0] == str(cset): + hgbranch[str(cset)] = hgbranch[parent] + else: + hgbranch[str(cset)] = "branch-" + str(cset) + +if not hgvers.has_key("0"): + print 'creating repository' + os.system('git-init-db') + +# loop through every hg changeset +for cset in range(int(tip) + 1): + + # incremental, already seen + if hgvers.has_key(str(cset)): + continue + + # get info + prnts = os.popen('hg log -r %d | grep ^parent: | cut -f 2 -d :' % cset).readlines() + if len(prnts) > 0: + parent = prnts[0].strip() + else: + parent = str(cset - 1) + if len(prnts) > 1: + mparent = prnts[1].strip() + else: + mparent = None + + (fdcomment, filecomment) = tempfile.mkstemp() + csetcomment = os.popen('hg log -r %d -v | grep -v ^changeset: | grep -v ^parent: | grep -v ^user: | grep -v ^date | grep -v ^files: | grep -v ^description: | grep -v ^tag:' % cset).read().strip() + os.write(fdcomment, csetcomment) + os.close(fdcomment) + + date = os.popen('hg log -r %d | grep ^date: | cut -f 2- -d :' % cset).read().strip() + + tag = os.popen('hg log -r %d | grep ^tag: | cut -f 2- -d :' % cset).read().strip() + + user = os.popen('hg log -r %d | grep ^user: | cut -f 2- -d :' % cset).read().strip() + + print '-----------------------------------------' + print 'cset:', cset + print 'branch:', hgbranch[str(cset)] + print 'user:', user + print 'date:', date + print 'comment:', csetcomment + print 'parent:', parent + if mparent: + print 'mparent:', mparent + if tag: + print 'tag:', tag + print '-----------------------------------------' + + # checkout the parent if necessary + if cset != 0: + if hgbranch[str(cset)] == "branch-" + str(cset): + print 'creating new branch', hgbranch[str(cset)] + os.system('git-checkout -b %s %s' % (hgbranch[str(cset)], hgvers[parent])) + else: + print 'checking out branch', hgbranch[str(cset)] + os.system('git-checkout %s' % hgbranch[str(cset)]) + + # merge + if mparent: + if hgbranch[parent] == hgbranch[str(cset)]: + otherbranch = hgbranch[mparent] + else: + otherbranch = hgbranch[parent] + print 'merging', otherbranch, 'into', hgbranch[str(cset)] + os.system(getgitenv(user, date) + 'git-merge --no-commit -s ours "" %s %s' % (hgbranch[str(cset)], otherbranch)) + + # remove everything except .git and .hg directories + os.system('find . \( -path "./.hg" -o -path "./.git" \) -prune -o ! -name "." -print | xargs rm -rf') + + # repopulate with checkouted files + os.system('hg update -C %d' % cset) + + # add new files + os.system('git-ls-files -x .hg --others | git-update-index --add --stdin') + # delete removed files + os.system('git-ls-files -x .hg --deleted | git-update-index --remove --stdin') + + # commit + os.system(getgitenv(user, date) + 'git-commit -a -F %s' % filecomment) + os.unlink(filecomment) + + # tag + if tag and tag != 'tip': + os.system(getgitenv(user, date) + 'git-tag %s' % tag) + + # delete branch if not used anymore... + if mparent and len(hgchildren[str(cset)]): + print "Deleting unused branch:", otherbranch + os.system('git-branch -d %s' % otherbranch) + + # retrieve and record the version + vvv = os.popen('git-show | head -1').read() + vvv = vvv[vvv.index(' ') + 1 : ].strip() + print 'record', cset, '->', vvv + hgvers[str(cset)] = vvv + +os.system('git-repack -a -d') + +# write the state for incrementals +if state: + print 'Writing state' + f = open(state, 'w') + pickle.dump(hgvers, f) + +# vim: et ts=8 sw=4 sts=4 diff --git a/contrib/hg-to-git/hg-to-git.txt b/contrib/hg-to-git/hg-to-git.txt new file mode 100644 index 0000000000..91f8fe6410 --- /dev/null +++ b/contrib/hg-to-git/hg-to-git.txt @@ -0,0 +1,21 @@ +hg-to-git.py is able to convert a Mercurial repository into a git one, +and preserves the branches in the process (unlike tailor) + +hg-to-git.py can probably be greatly improved (it's a rather crude +combination of shell and python) but it does already work quite well for +me. Features: + - supports incremental conversion + (for keeping a git repo in sync with a hg one) + - supports hg branches + - converts hg tags + +Note that the git repository will be created 'in place' (at the same +location as the source hg repo). You will have to manually remove the +'.hg' directory after the conversion. + +Also note that the incremental conversion uses 'simple' hg changesets +identifiers (ordinals, as opposed to SHA-1 ids), and since these ids +are not stable across different repositories the hg-to-git.py state file +is forever tied to one hg repository. + +Stelian Pop diff --git a/diff-lib.c b/diff-lib.c index 2c9be60ed9..91cd87742f 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -7,6 +7,7 @@ #include "diff.h" #include "diffcore.h" #include "revision.h" +#include "cache-tree.h" /* * diff-files @@ -271,7 +272,7 @@ static int diff_cache(struct rev_info *revs, break; } /* Show difference between old and new */ - show_modified(revs,ac[1], ce, 1, + show_modified(revs, ac[1], ce, 1, cached, match_missing); break; case 1: @@ -372,3 +373,44 @@ int run_diff_index(struct rev_info *revs, int cached) diff_flush(&revs->diffopt); return ret; } + +int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) +{ + struct tree *tree; + struct rev_info revs; + int i; + struct cache_entry **dst; + struct cache_entry *last = NULL; + + /* + * This is used by git-blame to run diff-cache internally; + * it potentially needs to repeatedly run this, so we will + * start by removing the higher order entries the last round + * left behind. + */ + dst = active_cache; + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (ce_stage(ce)) { + if (last && !strcmp(ce->name, last->name)) + continue; + cache_tree_invalidate_path(active_cache_tree, + ce->name); + last = ce; + ce->ce_mode = 0; + ce->ce_flags &= ~htons(CE_STAGEMASK); + } + *dst++ = ce; + } + active_nr = dst - active_cache; + + init_revisions(&revs, NULL); + revs.prune_data = opt->paths; + tree = parse_tree_indirect(tree_sha1); + if (!tree) + die("bad tree object %s", sha1_to_hex(tree_sha1)); + if (read_tree(tree, 1, opt->paths)) + return error("unable to read tree %s", sha1_to_hex(tree_sha1)); + return diff_cache(&revs, active_cache, active_nr, revs.prune_data, + 1, 0); +} diff --git a/diff.h b/diff.h index 7a347cf77d..eece65ddcc 100644 --- a/diff.h +++ b/diff.h @@ -222,6 +222,7 @@ extern int run_diff_files(struct rev_info *revs, int silent_on_removed); extern int run_diff_index(struct rev_info *revs, int cached); +extern int do_diff_cache(const unsigned char *, struct diff_options *); extern int diff_flush_patch_id(struct diff_options *, unsigned char *); #endif /* DIFF_H */ diff --git a/git-bisect.sh b/git-bisect.sh index e8d3418988..24160ec51c 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -11,6 +11,7 @@ git bisect replay replay bisection log git bisect log show bisect log.' . git-sh-setup +require_work_tree sq() { @@PERL@@ -e ' diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index 4863c91fe3..870554eade 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -15,9 +15,9 @@ unless ($ENV{GIT_DIR} && -r $ENV{GIT_DIR}){ die "GIT_DIR is not defined or is unreadable"; } -our ($opt_h, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m ); +our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m ); -getopts('hpvcfam:'); +getopts('hPpvcfam:'); $opt_h && usage(); @@ -89,7 +89,7 @@ if ($parent) { last; }; # found it } - die "Did not find $parent in the parents for this commit!" if !$found; + die "Did not find $parent in the parents for this commit!" if !$found and !$opt_P; } else { # we don't have a parent from the cmdline... if (@parents == 1) { # it's safe to get it from the commit $parent = $parents[0]; diff --git a/git-remote.perl b/git-remote.perl index f16ff21b8b..c56c5a84a4 100755 --- a/git-remote.perl +++ b/git-remote.perl @@ -253,14 +253,30 @@ sub show_remote { } sub add_remote { - my ($name, $url) = @_; + my ($name, $url, $opts) = @_; if (exists $remote->{$name}) { print STDERR "remote $name already exists.\n"; exit(1); } $git->command('config', "remote.$name.url", $url); - $git->command('config', "remote.$name.fetch", - "+refs/heads/*:refs/remotes/$name/*"); + my $track = $opts->{'track'} || ["*"]; + + for (@$track) { + $git->command('config', '--add', "remote.$name.fetch", + "+refs/heads/$_:refs/remotes/$name/$_"); + } + if ($opts->{'fetch'}) { + $git->command('fetch', $name); + } + if (exists $opts->{'master'}) { + $git->command('symbolic-ref', "refs/remotes/$name/HEAD", + "refs/remotes/$name/$opts->{'master'}"); + } +} + +sub add_usage { + print STDERR "Usage: git remote add [-f] [-t track]* [-m master] \n"; + exit(1); } if (!@ARGV) { @@ -307,11 +323,37 @@ elsif ($ARGV[0] eq 'prune') { } } elsif ($ARGV[0] eq 'add') { - if (@ARGV != 3) { - print STDERR "Usage: git remote add \n"; - exit(1); + my %opts = (); + while (1 < @ARGV && $ARGV[1] =~ /^-/) { + my $opt = $ARGV[1]; + shift @ARGV; + if ($opt eq '-f' || $opt eq '--fetch') { + $opts{'fetch'} = 1; + next; + } + if ($opt eq '-t' || $opt eq '--track') { + if (@ARGV < 1) { + add_usage(); + } + $opts{'track'} ||= []; + push @{$opts{'track'}}, $ARGV[1]; + shift @ARGV; + next; + } + if ($opt eq '-m' || $opt eq '--master') { + if ((@ARGV < 1) || exists $opts{'master'}) { + add_usage(); + } + $opts{'master'} = $ARGV[1]; + shift @ARGV; + next; + } + add_usage(); } - add_remote($ARGV[1], $ARGV[2]); + if (@ARGV != 3) { + add_usage(); + } + add_remote($ARGV[1], $ARGV[2], \%opts); } else { print STDERR "Usage: git remote\n"; diff --git a/git-sh-setup.sh b/git-sh-setup.sh index b4aa4b2f4e..f24c7f2d23 100755 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -48,7 +48,8 @@ cd_to_toplevel () { } require_work_tree () { - test $(is_bare_repository) = false || + test $(is_bare_repository) = false && + test $(git-rev-parse --is-inside-git-dir) = false || die "fatal: $0 cannot be used without a working tree." } diff --git a/git.c b/git.c index 564ca7cd2a..892b9cb8c7 100644 --- a/git.c +++ b/git.c @@ -300,8 +300,9 @@ static void handle_internal_command(int argc, const char **argv, char **envp) prefix = setup_git_directory(); if (p->option & USE_PAGER) setup_pager(); - if ((p->option & NOT_BARE) && is_bare_repository()) - die("%s cannot be used in a bare git directory", cmd); + if ((p->option & NOT_BARE) && + (is_bare_repository() || is_inside_git_dir())) + die("%s must be run in a work tree", cmd); trace_argv_printf(argv, argc, "trace: built-in: git"); exit(p->fn(argc, argv, prefix)); diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index a2076a680c..d374110070 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1690,7 +1690,7 @@ sub git_header_html { my $title = "$site_name"; if (defined $project) { - $title .= " - $project"; + $title .= " - " . to_utf8($project); if (defined $action) { $title .= "/$action"; if (defined $file_name) { @@ -1963,7 +1963,7 @@ sub git_print_page_path { print "
"; print $cgi->a({-href => href(action=>"tree", hash_base=>$hb), - -title => 'tree root'}, "[$project]"); + -title => 'tree root'}, to_utf8("[$project]"); print " / "; if (defined $name) { my @dirname = split '/', $name; @@ -3610,7 +3610,7 @@ sub git_snapshot { $hash = git_get_head_hash($project); } - my $filename = basename($project) . "-$hash.tar.$suffix"; + my $filename = to_utf8(basename($project)) . "-$hash.tar.$suffix"; print $cgi->header( -type => "application/$ctype", diff --git a/merge-recursive.c b/merge-recursive.c index a68fcc6f37..58989424d7 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1213,7 +1213,7 @@ static int merge(struct commit *h1, tree->object.parsed = 1; tree->object.type = OBJ_TREE; - write_sha1_file(NULL, 0, tree_type, tree->object.sha1); + pretend_sha1_file(NULL, 0, tree_type, tree->object.sha1); merged_common_ancestors = make_virtual_commit(tree, "ancestor"); } diff --git a/pager.c b/pager.c index 4587fbbdb5..5f280ab527 100644 --- a/pager.c +++ b/pager.c @@ -1,5 +1,7 @@ #include "cache.h" +#include + /* * This is split up from the rest of git so that we might do * something different on Windows, for example. @@ -7,6 +9,16 @@ static void run_pager(const char *pager) { + /* + * Work around bug in "less" by not starting it until we + * have real input + */ + fd_set in; + + FD_ZERO(&in); + FD_SET(0, &in); + select(1, &in, NULL, &in, NULL); + execlp(pager, pager, NULL); execl("/bin/sh", "sh", "-c", pager, NULL); } diff --git a/sha1_file.c b/sha1_file.c index 1526a28095..0d4bf80e74 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1505,10 +1505,67 @@ static void *read_packed_sha1(const unsigned char *sha1, char *type, unsigned lo return unpack_entry(e.p, e.offset, type, size); } +/* + * This is meant to hold a *small* number of objects that you would + * want read_sha1_file() to be able to return, but yet you do not want + * to write them into the object store (e.g. a browse-only + * application). + */ +static struct cached_object { + unsigned char sha1[20]; + const char *type; + void *buf; + unsigned long size; +} *cached_objects; +static int cached_object_nr, cached_object_alloc; + +static struct cached_object *find_cached_object(const unsigned char *sha1) +{ + int i; + struct cached_object *co = cached_objects; + + for (i = 0; i < cached_object_nr; i++, co++) { + if (!hashcmp(co->sha1, sha1)) + return co; + } + return NULL; +} + +int pretend_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *sha1) +{ + struct cached_object *co; + + hash_sha1_file(buf, len, type, sha1); + if (has_sha1_file(sha1) || find_cached_object(sha1)) + return 0; + if (cached_object_alloc <= cached_object_nr) { + cached_object_alloc = alloc_nr(cached_object_alloc); + cached_objects = xrealloc(cached_objects, + sizeof(*cached_objects) * + cached_object_alloc); + } + co = &cached_objects[cached_object_nr++]; + co->size = len; + co->type = strdup(type); + hashcpy(co->sha1, sha1); + return 0; +} + void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size) { unsigned long mapsize; void *map, *buf; + struct cached_object *co; + + co = find_cached_object(sha1); + if (co) { + buf = xmalloc(co->size + 1); + memcpy(buf, co->buf, co->size); + ((char*)buf)[co->size] = 0; + strcpy(type, co->type); + *size = co->size; + return buf; + } buf = read_packed_sha1(sha1, type, size); if (buf) diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh index c443f32fce..4efa0c926c 100755 --- a/t/t9200-git-cvsexportcommit.sh +++ b/t/t9200-git-cvsexportcommit.sh @@ -169,6 +169,16 @@ test_expect_success \ test "$(echo $(sort "G g/CVS/Entries"|cut -d/ -f2,3,5))" = "with spaces.png/1.2/-kb with spaces.txt/1.2/" )' +# Some filesystems mangle pathnames with UTF-8 characters -- +# check and skip +if p="Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö" && + mkdir -p "tst/$p" && + date >"tst/$p/day" && + found=$(find tst -type f -print) && + test "z$found" = "ztst/$p/day" && + rm -fr tst +then + # This test contains UTF-8 characters test_expect_success \ 'File with non-ascii file name' \ @@ -184,6 +194,10 @@ test_expect_success \ test "$(echo $(sort Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/CVS/Entries|cut -d/ -f2,3,5))" = "gårdetsågårdet.png/1.1/-kb gårdetsågårdet.txt/1.1/" )' +fi + +rm -fr tst + test_expect_success \ 'Mismatching patch should fail' \ 'date >>"E/newfile5.txt" &&