mirror of
https://github.com/git/git.git
synced 2026-03-16 11:40:07 +01:00
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:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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`).
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
------
|
||||
|
||||
|
||||
209
builtin-blame.c
209
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 <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);
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
|
||||
1
cache.h
1
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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
180
contrib/emacs/git-blame.el
Normal 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
233
contrib/hg-to-git/hg-to-git.py
Executable 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
|
||||
21
contrib/hg-to-git/hg-to-git.txt
Normal file
21
contrib/hg-to-git/hg-to-git.txt
Normal 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>
|
||||
44
diff-lib.c
44
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);
|
||||
}
|
||||
|
||||
1
diff.h
1
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 */
|
||||
|
||||
@@ -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 '
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
5
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));
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
12
pager.c
@@ -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);
|
||||
}
|
||||
|
||||
57
sha1_file.c
57
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)
|
||||
|
||||
@@ -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" &&
|
||||
|
||||
Reference in New Issue
Block a user