Merge branch 'master' into next

* master: (22 commits)
  pager: Work around window resizing bug in 'less'
  Teach git-remote add to fetch and track
  blame: document --contents option
  Use pretend_sha1_file() in git-blame and git-merge-recursive.
  Add pretend_sha1_file() interface.
  git-blame: no rev means start from the working tree file.
  git-blame: an Emacs minor mode to view file with git-blame output.
  Allow forcing of a parent commit, even if the parent is not a direct one.
  bisect: it needs to be done in a working tree.
  Commands requiring a work tree must not run in GIT_DIR
  Add hg-to-git conversion utility.
  blameview: Support browsable functionality to blameview.
  gitweb: Convert project name to UTF-8
  bash: Support git-bisect and its subcommands.
  bash: Support --add completion to git-config.
  bash: Hide git-resolve, its deprecated.
  bash: Offer --prune completion for git-gc.
  bash: Hide diff-stages from completion.
  bash: Support completion on git-cherry.
  Show an example of deleting commits with git-rebase.
  ...
This commit is contained in:
Junio C Hamano
2007-02-05 15:47:13 -08:00
26 changed files with 944 additions and 53 deletions

View File

@@ -9,7 +9,7 @@ SYNOPSIS
--------
[verse]
'git-blame' [-c] [-l] [-t] [-f] [-n] [-p] [--incremental] [-L n,m] [-S <revs-file>]
[-M] [-C] [-C] [--since=<date>] [<rev>] [--] <file>
[-M] [-C] [-C] [--since=<date>] [<rev> | --contents <file>] [--] <file>
DESCRIPTION
-----------
@@ -67,6 +67,13 @@ OPTIONS
Show the result incrementally in a format designed for
machine consumption.
--contents <file>::
When <rev> 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

View File

@@ -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.

View File

@@ -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`).

View File

@@ -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 <upstream>
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
<newbase>::
Starting point at which to create the new commits. If the
--onto option is not specified, the starting point is
<upstream>.
<upstream>. May be any valid commit, and not just an
existing branch name.
<upstream>::
Upstream branch to compare against.
Upstream branch to compare against. May be any valid commit,
not just an existing branch name.
<branch>::
Working branch; defaults to HEAD.

View File

@@ -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
------

View File

@@ -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 <revs-file>] [-M] [-C] [-C] [commit] [--] file\n"
"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S <revs-file>] [-M] [-C] [-C] [--contents <filename>] [--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 <file>'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);

View File

@@ -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 */

View File

@@ -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;

View File

@@ -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);

View File

@@ -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 [<rev>] <filename>";
}
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);

View File

@@ -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

180
contrib/emacs/git-blame.el Normal file
View File

@@ -0,0 +1,180 @@
;;; git-blame.el
;; David Kågedal <davidk@lysator.liu.se>
;; 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)))))

233
contrib/hg-to-git/hg-to-git.py Executable file
View File

@@ -0,0 +1,233 @@
#! /usr/bin/python
""" hg-to-svn.py - A Mercurial to GIT converter
Copyright (C)2007 Stelian Pop <stelian@popies.net>
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] <hgprj>
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

View File

@@ -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 <stelian@popies.net>

View File

@@ -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);
}

1
diff.h
View File

@@ -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 */

View File

@@ -11,6 +11,7 @@ git bisect replay <logfile> replay bisection log
git bisect log show bisect log.'
. git-sh-setup
require_work_tree
sq() {
@@PERL@@ -e '

View File

@@ -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];

View File

@@ -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] <name> <url>\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 <name> <url>\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";

View File

@@ -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."
}

5
git.c
View File

@@ -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));

View File

@@ -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 "<div class=\"page_path\">";
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",

View File

@@ -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");
}

12
pager.c
View File

@@ -1,5 +1,7 @@
#include "cache.h"
#include <sys/select.h>
/*
* 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);
}

View File

@@ -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)

View File

@@ -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" &&