From cf0adba7885342e1bbcf0689fece9d13e39784b4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 19 Nov 2006 13:22:44 -0800 Subject: Store peeled refs in packed-refs file. This would speed up "show-ref -d" in a repository with mostly packed tags. Signed-off-by: Junio C Hamano diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c index 042d271..ee5a556 100644 --- a/builtin-pack-refs.c +++ b/builtin-pack-refs.c @@ -1,5 +1,7 @@ #include "cache.h" #include "refs.h" +#include "object.h" +#include "tag.h" static const char builtin_pack_refs_usage[] = "git-pack-refs [--all] [--prune]"; @@ -29,12 +31,26 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data) { struct pack_refs_cb_data *cb = cb_data; + int is_tag_ref; - if (!cb->all && strncmp(path, "refs/tags/", 10)) - return 0; /* Do not pack the symbolic refs */ - if (!(flags & REF_ISSYMREF)) - fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path); + if ((flags & REF_ISSYMREF)) + return 0; + is_tag_ref = !strncmp(path, "refs/tags/", 10); + if (!cb->all && !is_tag_ref) + return 0; + + fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path); + if (is_tag_ref) { + struct object *o = parse_object(sha1); + if (o->type == OBJ_TAG) { + o = deref_tag(o, path, 0); + if (o) + fprintf(cb->refs_file, "%s %s^{}\n", + sha1_to_hex(o->sha1), path); + } + } + if (cb->prune && !do_not_prune(flags)) { int namelen = strlen(path) + 1; struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen); diff --git a/builtin-show-ref.c b/builtin-show-ref.c index 06ec400..9ae3d08 100644 --- a/builtin-show-ref.c +++ b/builtin-show-ref.c @@ -13,6 +13,7 @@ static int show_ref(const char *refname, const unsigned char *sha1, int flag, vo { struct object *obj; const char *hex; + unsigned char peeled[20]; if (tags_only || heads_only) { int match; @@ -44,12 +45,15 @@ static int show_ref(const char *refname, const unsigned char *sha1, int flag, vo match: found_match++; - obj = parse_object(sha1); - if (!obj) { - if (quiet) - return 0; - die("git-show-ref: bad ref %s (%s)", refname, sha1_to_hex(sha1)); - } + + /* This changes the semantics slightly that even under quiet we + * detect and return error if the repository is corrupt and + * ref points at a nonexistent object. + */ + if (!has_sha1_file(sha1)) + die("git-show-ref: bad ref %s (%s)", refname, + sha1_to_hex(sha1)); + if (quiet) return 0; @@ -58,11 +62,25 @@ match: printf("%s\n", hex); else printf("%s %s\n", hex, refname); - if (deref_tags && obj->type == OBJ_TAG) { - obj = deref_tag(obj, refname, 0); - hex = find_unique_abbrev(obj->sha1, abbrev); + + if (!deref_tags) + return 0; + + if ((flag & REF_ISPACKED) && !peel_ref(refname, peeled)) { + hex = find_unique_abbrev(peeled, abbrev); printf("%s %s^{}\n", hex, refname); } + else { + obj = parse_object(sha1); + if (!obj) + die("git-show-ref: bad ref %s (%s)", refname, + sha1_to_hex(sha1)); + if (obj->type == OBJ_TAG) { + obj = deref_tag(obj, refname, 0); + hex = find_unique_abbrev(obj->sha1, abbrev); + printf("%s %s^{}\n", hex, refname); + } + } return 0; } diff --git a/refs.c b/refs.c index 0e156c5..75cbc0e 100644 --- a/refs.c +++ b/refs.c @@ -1,16 +1,18 @@ #include "refs.h" #include "cache.h" +#include "object.h" +#include "tag.h" #include struct ref_list { struct ref_list *next; - unsigned char flag; /* ISSYMREF? ISPACKED? */ + unsigned char flag; /* ISSYMREF? ISPACKED? ISPEELED? */ unsigned char sha1[20]; char name[FLEX_ARRAY]; }; -static const char *parse_ref_line(char *line, unsigned char *sha1) +static const char *parse_ref_line(char *line, unsigned char *sha1, int *flag) { /* * 42: the answer to everything. @@ -21,6 +23,7 @@ static const char *parse_ref_line(char *line, unsigned char *sha1) * +1 (newline at the end of the line) */ int len = strlen(line) - 42; + int peeled = 0; if (len <= 0) return NULL; @@ -29,11 +32,24 @@ static const char *parse_ref_line(char *line, unsigned char *sha1) if (!isspace(line[40])) return NULL; line += 41; - if (isspace(*line)) - return NULL; + + if (isspace(*line)) { + /* "SHA-1 SP SP refs/tags/tagname^{} LF"? */ + line++; + len--; + peeled = 1; + } if (line[len] != '\n') return NULL; line[len] = 0; + + if (peeled && (len < 3 || strcmp(line + len - 3, "^{}"))) + return NULL; + + if (!peeled) + *flag &= ~REF_ISPEELED; + else + *flag |= REF_ISPEELED; return line; } @@ -108,10 +124,12 @@ static struct ref_list *get_packed_refs(void) char refline[PATH_MAX]; while (fgets(refline, sizeof(refline), f)) { unsigned char sha1[20]; - const char *name = parse_ref_line(refline, sha1); + int flag = REF_ISPACKED; + const char *name = + parse_ref_line(refline, sha1, &flag); if (!name) continue; - list = add_ref(name, sha1, REF_ISPACKED, list); + list = add_ref(name, sha1, flag, list); } fclose(f); refs = list; @@ -207,7 +225,8 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int * if (lstat(path, &st) < 0) { struct ref_list *list = get_packed_refs(); while (list) { - if (!strcmp(ref, list->name)) { + if (!(list->flag & REF_ISPEELED) && + !strcmp(ref, list->name)) { hashcpy(sha1, list->sha1); if (flag) *flag |= REF_ISPACKED; @@ -329,6 +348,8 @@ static int do_one_ref(const char *base, each_ref_fn fn, int trim, return 0; if (is_null_sha1(entry->sha1)) return 0; + if (entry->flag & REF_ISPEELED) + return 0; if (!has_sha1_file(entry->sha1)) { error("%s does not point to a valid object!", entry->name); return 0; @@ -336,6 +357,44 @@ static int do_one_ref(const char *base, each_ref_fn fn, int trim, return fn(entry->name + trim, entry->sha1, entry->flag, cb_data); } +int peel_ref(const char *ref, unsigned char *sha1) +{ + int flag; + unsigned char base[20]; + struct object *o; + + if (!resolve_ref(ref, base, 1, &flag)) + return -1; + + if ((flag & REF_ISPACKED)) { + struct ref_list *list = get_packed_refs(); + int len = strlen(ref); + + while (list) { + if ((list->flag & REF_ISPEELED) && + !strncmp(list->name, ref, len) && + strlen(list->name) == len + 3 && + !strcmp(list->name + len, "^{}")) { + hashcpy(sha1, list->sha1); + return 0; + } + list = list->next; + } + /* older pack-refs did not leave peeled ones in */ + } + + /* otherwise ... */ + o = parse_object(base); + if (o->type == OBJ_TAG) { + o = deref_tag(o, ref, 0); + if (o) { + hashcpy(sha1, o->sha1); + return 0; + } + } + return -1; +} + static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, void *cb_data) { diff --git a/refs.h b/refs.h index a57d437..40048a6 100644 --- a/refs.h +++ b/refs.h @@ -16,6 +16,8 @@ struct ref_lock { */ #define REF_ISSYMREF 01 #define REF_ISPACKED 02 +#define REF_ISPEELED 04 /* internal use */ + typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data); extern int head_ref(each_ref_fn, void *); extern int for_each_ref(each_ref_fn, void *); @@ -23,6 +25,8 @@ extern int for_each_tag_ref(each_ref_fn, void *); extern int for_each_branch_ref(each_ref_fn, void *); extern int for_each_remote_ref(each_ref_fn, void *); +extern int peel_ref(const char *, unsigned char *); + /** Reads the refs file specified into sha1 **/ extern int get_ref_sha1(const char *ref, unsigned char *sha1); -- cgit v0.10.2-6-g49f6 From b8ec59234ba2c1833e29eece9ed87f7a471cbae2 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 22 Oct 2006 13:23:31 +0200 Subject: Build in shortlog [jc: with minimum squelching of compiler warning under "-pedantic" compilation options.] Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt index d54fc3e..95fa901 100644 --- a/Documentation/git-shortlog.txt +++ b/Documentation/git-shortlog.txt @@ -8,6 +8,7 @@ git-shortlog - Summarize 'git log' output SYNOPSIS -------- git-log --pretty=short | 'git-shortlog' [-h] [-n] [-s] +git-shortlog [-n|--number] [-s|--summary] [...] DESCRIPTION ----------- diff --git a/Makefile b/Makefile index 36ce8cd..6fd28b0 100644 --- a/Makefile +++ b/Makefile @@ -174,7 +174,7 @@ SCRIPT_SH = \ SCRIPT_PERL = \ git-archimport.perl git-cvsimport.perl git-relink.perl \ - git-shortlog.perl git-rerere.perl \ + git-rerere.perl \ git-cvsserver.perl \ git-svnimport.perl git-cvsexportcommit.perl \ git-send-email.perl git-svn.perl @@ -300,6 +300,7 @@ BUILTIN_OBJS = \ builtin-rev-parse.o \ builtin-rm.o \ builtin-runstatus.o \ + builtin-shortlog.o \ builtin-show-branch.o \ builtin-stripspace.o \ builtin-symbolic-ref.o \ diff --git a/builtin-shortlog.c b/builtin-shortlog.c new file mode 100644 index 0000000..48a2a0b --- /dev/null +++ b/builtin-shortlog.c @@ -0,0 +1,302 @@ +#include "builtin.h" +#include "cache.h" +#include "commit.h" +#include "diff.h" +#include "path-list.h" +#include "revision.h" +#include + +static const char shortlog_usage[] = +"git-shortlog [-n] [-s] [... ]\n"; + +static int compare_by_number(const void *a1, const void *a2) +{ + const struct path_list_item *i1 = a1, *i2 = a2; + const struct path_list *l1 = i1->util, *l2 = i2->util; + + if (l1->nr < l2->nr) + return -1; + else if (l1->nr == l2->nr) + return 0; + else + return +1; +} + +static struct path_list_item mailmap_list[] = { + { "R.Marek@sh.cvut.cz", (void*)"Rudolf Marek" }, + { "Ralf.Wildenhues@gmx.de", (void*)"Ralf Wildenhues" }, + { "aherrman@de.ibm.com", (void*)"Andreas Herrmann" }, + { "akpm@osdl.org", (void*)"Andrew Morton" }, + { "andrew.vasquez@qlogic.com", (void*)"Andrew Vasquez" }, + { "aquynh@gmail.com", (void*)"Nguyen Anh Quynh" }, + { "axboe@suse.de", (void*)"Jens Axboe" }, + { "blaisorblade@yahoo.it", (void*)"Paolo 'Blaisorblade' Giarrusso" }, + { "bunk@stusta.de", (void*)"Adrian Bunk" }, + { "domen@coderock.org", (void*)"Domen Puncer" }, + { "dougg@torque.net", (void*)"Douglas Gilbert" }, + { "dwmw2@shinybook.infradead.org", (void*)"David Woodhouse" }, + { "ecashin@coraid.com", (void*)"Ed L Cashin" }, + { "felix@derklecks.de", (void*)"Felix Moeller" }, + { "fzago@systemfabricworks.com", (void*)"Frank Zago" }, + { "gregkh@suse.de", (void*)"Greg Kroah-Hartman" }, + { "hch@lst.de", (void*)"Christoph Hellwig" }, + { "htejun@gmail.com", (void*)"Tejun Heo" }, + { "jejb@mulgrave.(none)", (void*)"James Bottomley" }, + { "jejb@titanic.il.steeleye.com", (void*)"James Bottomley" }, + { "jgarzik@pretzel.yyz.us", (void*)"Jeff Garzik" }, + { "johnpol@2ka.mipt.ru", (void*)"Evgeniy Polyakov" }, + { "kay.sievers@vrfy.org", (void*)"Kay Sievers" }, + { "minyard@acm.org", (void*)"Corey Minyard" }, + { "mshah@teja.com", (void*)"Mitesh shah" }, + { "pj@ludd.ltu.se", (void*)"Peter A Jonsson" }, + { "rmps@joel.ist.utl.pt", (void*)"Rui Saraiva" }, + { "santtu.hyrkko@gmail.com", (void*)"Santtu Hyrkk,Av(B" }, + { "simon@thekelleys.org.uk", (void*)"Simon Kelley" }, + { "ssant@in.ibm.com", (void*)"Sachin P Sant" }, + { "terra@gnome.org", (void*)"Morten Welinder" }, + { "tony.luck@intel.com", (void*)"Tony Luck" }, + { "welinder@anemone.rentec.com", (void*)"Morten Welinder" }, + { "welinder@darter.rentec.com", (void*)"Morten Welinder" }, + { "welinder@troll.com", (void*)"Morten Welinder" } +}; + +static struct path_list mailmap = { + mailmap_list, + sizeof(mailmap_list) / sizeof(struct path_list_item), 0, 0 +}; + +static int map_email(char *email, char *name, int maxlen) +{ + char *p; + struct path_list_item *item; + + /* autocomplete common developers */ + p = strchr(email, '>'); + if (!p) + return 0; + + *p = '\0'; + item = path_list_lookup(email, &mailmap); + if (item != NULL) { + const char *realname = (const char *)item->util; + strncpy(name, realname, maxlen); + return 1; + } + return 0; +} + +static void insert_author_oneline(struct path_list *list, + const char *author, int authorlen, + const char *oneline, int onelinelen) +{ + const char *dot3 = "/pub/scm/linux/kernel/git/"; + char *buffer, *p; + struct path_list_item *item; + struct path_list *onelines; + + while (authorlen > 0 && isspace(author[authorlen - 1])) + authorlen--; + + buffer = xmalloc(authorlen + 1); + memcpy(buffer, author, authorlen); + buffer[authorlen] = '\0'; + + item = path_list_insert(buffer, list); + if (item->util == NULL) + item->util = xcalloc(1, sizeof(struct path_list)); + else + free(buffer); + + if (!strncmp(oneline, "[PATCH", 6)) { + char *eob = strchr(buffer, ']'); + + while (isspace(eob[1]) && eob[1] != '\n') + eob++; + if (eob - oneline < onelinelen) { + onelinelen -= eob - oneline; + oneline = eob; + } + } + + while (onelinelen > 0 && isspace(oneline[0])) { + oneline++; + onelinelen--; + } + + while (onelinelen > 0 && isspace(oneline[onelinelen - 1])) + onelinelen--; + + buffer = xmalloc(onelinelen + 1); + memcpy(buffer, oneline, onelinelen); + buffer[onelinelen] = '\0'; + + while ((p = strstr(buffer, dot3)) != NULL) { + memcpy(p, "...", 3); + strcpy(p + 2, p + sizeof(dot3) - 1); + } + + + onelines = item->util; + if (onelines->nr >= onelines->alloc) { + onelines->alloc = alloc_nr(onelines->nr); + onelines->items = xrealloc(onelines->items, + onelines->alloc + * sizeof(struct path_list_item)); + } + + onelines->items[onelines->nr].util = NULL; + onelines->items[onelines->nr++].path = buffer; +} + +static void read_from_stdin(struct path_list *list) +{ + char buffer[1024]; + + while (fgets(buffer, sizeof(buffer), stdin) != NULL) { + char *bob; + if ((buffer[0] == 'A' || buffer[0] == 'a') && + !strncmp(buffer + 1, "uthor: ", 7) && + (bob = strchr(buffer + 7, '<')) != NULL) { + char buffer2[1024], offset = 0; + + if (map_email(bob + 1, buffer, sizeof(buffer))) + bob = buffer + strlen(buffer); + else { + offset = 8; + while (isspace(bob[-1])) + bob--; + } + + while (fgets(buffer2, sizeof(buffer2), stdin) && + buffer2[0] != '\n') + ; /* chomp input */ + if (fgets(buffer2, sizeof(buffer2), stdin)) + insert_author_oneline(list, + buffer + offset, + bob - buffer - offset, + buffer2, strlen(buffer2)); + } + } +} + +static void get_from_rev(struct rev_info *rev, struct path_list *list) +{ + char scratch[1024]; + struct commit *commit; + + prepare_revision_walk(rev); + while ((commit = get_revision(rev)) != NULL) { + char *author = NULL, *oneline, *buffer; + int authorlen = authorlen, onelinelen; + + /* get author and oneline */ + for (buffer = commit->buffer; buffer && *buffer != '\0' && + *buffer != '\n'; ) { + char *eol = strchr(buffer, '\n'); + + if (eol == NULL) + eol = buffer + strlen(buffer); + else + eol++; + + if (!strncmp(buffer, "author ", 7)) { + char *bracket = strchr(buffer, '<'); + + if (bracket == NULL || bracket > eol) + die("Invalid commit buffer: %s", + sha1_to_hex(commit->object.sha1)); + + if (map_email(bracket + 1, scratch, + sizeof(scratch))) { + author = scratch; + authorlen = strlen(scratch); + } else { + while (bracket[-1] == ' ') + bracket--; + + author = buffer + 7; + authorlen = bracket - buffer - 7; + } + } + buffer = eol; + } + + if (author == NULL) + die ("Missing author: %s", + sha1_to_hex(commit->object.sha1)); + + if (buffer == NULL || *buffer == '\0') { + oneline = ""; + onelinelen = sizeof(oneline) + 1; + } else { + char *eol; + + oneline = buffer + 1; + eol = strchr(oneline, '\n'); + if (eol == NULL) + onelinelen = strlen(oneline); + else + onelinelen = eol - oneline; + } + + insert_author_oneline(list, + author, authorlen, oneline, onelinelen); + } + +} + +int cmd_shortlog(int argc, const char **argv, const char *prefix) +{ + struct rev_info rev; + struct path_list list = { NULL, 0, 0, 1 }; + int i, j, sort_by_number = 0, summary = 0; + + init_revisions(&rev, prefix); + argc = setup_revisions(argc, argv, &rev, NULL); + while (argc > 1) { + if (!strcmp(argv[1], "-n") || !strcmp(argv[1], "--numbered")) + sort_by_number = 1; + else if (!strcmp(argv[1], "-s") || + !strcmp(argv[1], "--summary")) + summary = 1; + else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) + usage(shortlog_usage); + else + die ("unrecognized argument: %s", argv[1]); + argv++; + argc--; + } + + if (rev.pending.nr == 1) + die ("Need a range!"); + else if (rev.pending.nr == 0) + read_from_stdin(&list); + else + get_from_rev(&rev, &list); + + if (sort_by_number) + qsort(list.items, sizeof(struct path_list_item), list.nr, + compare_by_number); + + for (i = 0; i < list.nr; i++) { + struct path_list *onelines = list.items[i].util; + + printf("%s (%d):\n", list.items[i].path, onelines->nr); + if (!summary) { + for (j = onelines->nr - 1; j >= 0; j--) + printf(" %s\n", onelines->items[j].path); + printf("\n"); + } + + onelines->strdup_paths = 1; + path_list_clear(onelines, 1); + free(onelines); + list.items[i].util = NULL; + } + + list.strdup_paths = 1; + path_list_clear(&list, 1); + + return 0; +} + diff --git a/builtin.h b/builtin.h index 43fed32..b5116f3 100644 --- a/builtin.h +++ b/builtin.h @@ -55,6 +55,7 @@ extern int cmd_rev_list(int argc, const char **argv, const char *prefix); extern int cmd_rev_parse(int argc, const char **argv, const char *prefix); extern int cmd_rm(int argc, const char **argv, const char *prefix); extern int cmd_runstatus(int argc, const char **argv, const char *prefix); +extern int cmd_shortlog(int argc, const char **argv, const char *prefix); extern int cmd_show(int argc, const char **argv, const char *prefix); extern int cmd_show_branch(int argc, const char **argv, const char *prefix); extern int cmd_stripspace(int argc, const char **argv, const char *prefix); diff --git a/git-shortlog.perl b/git-shortlog.perl deleted file mode 100755 index 334fec7..0000000 --- a/git-shortlog.perl +++ /dev/null @@ -1,234 +0,0 @@ -#!/usr/bin/perl -w - -use strict; -use Getopt::Std; -use File::Basename qw(basename dirname); - -our ($opt_h, $opt_n, $opt_s); -getopts('hns'); - -$opt_h && usage(); - -sub usage { - print STDERR "Usage: ${\basename $0} [-h] [-n] [-s] < \n"; - exit(1); -} - -my (%mailmap); -my (%email); -my (%map); -my $pstate = 1; -my $n_records = 0; -my $n_output = 0; - -sub shortlog_entry($$) { - my ($name, $desc) = @_; - my $key = $name; - - $desc =~ s#/pub/scm/linux/kernel/git/#/.../#g; - $desc =~ s#\[PATCH\] ##g; - - # store description in array, in email->{desc list} map - if (exists $map{$key}) { - # grab ref - my $obj = $map{$key}; - - # add desc to array - push(@$obj, $desc); - } else { - # create new array, containing 1 item - my @arr = ($desc); - - # store ref to array - $map{$key} = \@arr; - } -} - -# sort comparison function -sub by_name($$) { - my ($a, $b) = @_; - - uc($a) cmp uc($b); -} -sub by_nbentries($$) { - my ($a, $b) = @_; - my $a_entries = $map{$a}; - my $b_entries = $map{$b}; - - @$b_entries - @$a_entries || by_name $a, $b; -} - -my $sort_method = $opt_n ? \&by_nbentries : \&by_name; - -sub summary_output { - my ($obj, $num, $key); - - foreach $key (sort $sort_method keys %map) { - $obj = $map{$key}; - $num = @$obj; - printf "%s: %u\n", $key, $num; - $n_output += $num; - } -} - -sub shortlog_output { - my ($obj, $num, $key, $desc); - - foreach $key (sort $sort_method keys %map) { - $obj = $map{$key}; - $num = @$obj; - - # output author - printf "%s (%u):\n", $key, $num; - - # output author's 1-line summaries - foreach $desc (reverse @$obj) { - print " $desc\n"; - $n_output++; - } - - # blank line separating author from next author - print "\n"; - } -} - -sub changelog_input { - my ($author, $desc); - - while (<>) { - # get author and email - if ($pstate == 1) { - my ($email); - - next unless /^[Aa]uthor:?\s*(.*?)\s*<(.*)>/; - - $n_records++; - - $author = $1; - $email = $2; - $desc = undef; - - # cset author fixups - if (exists $mailmap{$email}) { - $author = $mailmap{$email}; - } elsif (exists $mailmap{$author}) { - $author = $mailmap{$author}; - } elsif (!$author) { - $author = $email; - } - $email{$author}{$email}++; - $pstate++; - } - - # skip to blank line - elsif ($pstate == 2) { - next unless /^\s*$/; - $pstate++; - } - - # skip to non-blank line - elsif ($pstate == 3) { - next unless /^\s*?(.*)/; - - # skip lines that are obviously not - # a 1-line cset description - next if /^\s*From: /; - - chomp; - $desc = $1; - - &shortlog_entry($author, $desc); - - $pstate = 1; - } - - else { - die "invalid parse state $pstate"; - } - } -} - -sub read_mailmap { - my ($fh, $mailmap) = @_; - while (<$fh>) { - chomp; - if (/^([^#].*?)\s*<(.*)>/) { - $mailmap->{$2} = $1; - } - } -} - -sub setup_mailmap { - read_mailmap(\*DATA, \%mailmap); - if (-f '.mailmap') { - my $fh = undef; - open $fh, '<', '.mailmap'; - read_mailmap($fh, \%mailmap); - close $fh; - } -} - -sub finalize { - #print "\n$n_records records parsed.\n"; - - if ($n_records != $n_output) { - die "parse error: input records != output records\n"; - } - if (0) { - for my $author (sort keys %email) { - my $e = $email{$author}; - for my $email (sort keys %$e) { - print STDERR "$author <$email>\n"; - } - } - } -} - -&setup_mailmap; -&changelog_input; -$opt_s ? &summary_output : &shortlog_output; -&finalize; -exit(0); - - -__DATA__ -# -# Even with git, we don't always have name translations. -# So have an email->real name table to translate the -# (hopefully few) missing names -# -Adrian Bunk -Andreas Herrmann -Andrew Morton -Andrew Vasquez -Christoph Hellwig -Corey Minyard -David Woodhouse -Domen Puncer -Douglas Gilbert -Ed L Cashin -Evgeniy Polyakov -Felix Moeller -Frank Zago -Greg Kroah-Hartman -James Bottomley -James Bottomley -Jeff Garzik -Jens Axboe -Kay Sievers -Mitesh shah -Morten Welinder -Morten Welinder -Morten Welinder -Morten Welinder -Nguyen Anh Quynh -Paolo 'Blaisorblade' Giarrusso -Peter A Jonsson -Ralf Wildenhues -Rudolf Marek -Rui Saraiva -Sachin P Sant -Santtu Hyrkk,Av(B -Simon Kelley -Tejun Heo -Tony Luck diff --git a/git.c b/git.c index 1aa07a5..f97de60 100644 --- a/git.c +++ b/git.c @@ -260,6 +260,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "rev-parse", cmd_rev_parse, RUN_SETUP }, { "rm", cmd_rm, RUN_SETUP }, { "runstatus", cmd_runstatus, RUN_SETUP }, + { "shortlog", cmd_shortlog, RUN_SETUP }, { "show-branch", cmd_show_branch, RUN_SETUP }, { "show", cmd_show, RUN_SETUP | USE_PAGER }, { "stripspace", cmd_stripspace }, -- cgit v0.10.2-6-g49f6 From 72019cdefeb6b8fd7e8bff37b9c087302a45e29e Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 19 Nov 2006 17:28:25 +0100 Subject: shortlog: do not crash on parsing "[PATCH" Annoyingly, it looked for the closing bracket in the author name instead of in the message, and then accessed the NULL pointer. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/builtin-shortlog.c b/builtin-shortlog.c index 48a2a0b..26212b0 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -108,13 +108,15 @@ static void insert_author_oneline(struct path_list *list, free(buffer); if (!strncmp(oneline, "[PATCH", 6)) { - char *eob = strchr(buffer, ']'); - - while (isspace(eob[1]) && eob[1] != '\n') - eob++; - if (eob - oneline < onelinelen) { - onelinelen -= eob - oneline; - oneline = eob; + char *eob = strchr(oneline, ']'); + + if (eob) { + while (isspace(eob[1]) && eob[1] != '\n') + eob++; + if (eob - oneline < onelinelen) { + onelinelen -= eob - oneline; + oneline = eob; + } } } -- cgit v0.10.2-6-g49f6 From d8e812502f1c72f5ef542de7eb05874e27f2b086 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 19 Nov 2006 17:28:51 +0100 Subject: shortlog: read mailmap from ./.mailmap again While at it, remove the linux specific mailmap into contrib/mailmap.linux. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/builtin-shortlog.c b/builtin-shortlog.c index 26212b0..afc9456 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -22,48 +22,40 @@ static int compare_by_number(const void *a1, const void *a2) return +1; } -static struct path_list_item mailmap_list[] = { - { "R.Marek@sh.cvut.cz", (void*)"Rudolf Marek" }, - { "Ralf.Wildenhues@gmx.de", (void*)"Ralf Wildenhues" }, - { "aherrman@de.ibm.com", (void*)"Andreas Herrmann" }, - { "akpm@osdl.org", (void*)"Andrew Morton" }, - { "andrew.vasquez@qlogic.com", (void*)"Andrew Vasquez" }, - { "aquynh@gmail.com", (void*)"Nguyen Anh Quynh" }, - { "axboe@suse.de", (void*)"Jens Axboe" }, - { "blaisorblade@yahoo.it", (void*)"Paolo 'Blaisorblade' Giarrusso" }, - { "bunk@stusta.de", (void*)"Adrian Bunk" }, - { "domen@coderock.org", (void*)"Domen Puncer" }, - { "dougg@torque.net", (void*)"Douglas Gilbert" }, - { "dwmw2@shinybook.infradead.org", (void*)"David Woodhouse" }, - { "ecashin@coraid.com", (void*)"Ed L Cashin" }, - { "felix@derklecks.de", (void*)"Felix Moeller" }, - { "fzago@systemfabricworks.com", (void*)"Frank Zago" }, - { "gregkh@suse.de", (void*)"Greg Kroah-Hartman" }, - { "hch@lst.de", (void*)"Christoph Hellwig" }, - { "htejun@gmail.com", (void*)"Tejun Heo" }, - { "jejb@mulgrave.(none)", (void*)"James Bottomley" }, - { "jejb@titanic.il.steeleye.com", (void*)"James Bottomley" }, - { "jgarzik@pretzel.yyz.us", (void*)"Jeff Garzik" }, - { "johnpol@2ka.mipt.ru", (void*)"Evgeniy Polyakov" }, - { "kay.sievers@vrfy.org", (void*)"Kay Sievers" }, - { "minyard@acm.org", (void*)"Corey Minyard" }, - { "mshah@teja.com", (void*)"Mitesh shah" }, - { "pj@ludd.ltu.se", (void*)"Peter A Jonsson" }, - { "rmps@joel.ist.utl.pt", (void*)"Rui Saraiva" }, - { "santtu.hyrkko@gmail.com", (void*)"Santtu Hyrkk,Av(B" }, - { "simon@thekelleys.org.uk", (void*)"Simon Kelley" }, - { "ssant@in.ibm.com", (void*)"Sachin P Sant" }, - { "terra@gnome.org", (void*)"Morten Welinder" }, - { "tony.luck@intel.com", (void*)"Tony Luck" }, - { "welinder@anemone.rentec.com", (void*)"Morten Welinder" }, - { "welinder@darter.rentec.com", (void*)"Morten Welinder" }, - { "welinder@troll.com", (void*)"Morten Welinder" } -}; - -static struct path_list mailmap = { - mailmap_list, - sizeof(mailmap_list) / sizeof(struct path_list_item), 0, 0 -}; +static struct path_list mailmap = {NULL, 0, 0, 0}; + +static int read_mailmap(const char *filename) +{ + char buffer[1024]; + FILE *f = fopen(filename, "r"); + + if (f == NULL) + return 1; + while (fgets(buffer, sizeof(buffer), f) != NULL) { + char *end_of_name, *left_bracket, *right_bracket; + char *name, *email; + if (buffer[0] == '#') + continue; + if ((left_bracket = strchr(buffer, '<')) == NULL) + continue; + if ((right_bracket = strchr(left_bracket + 1, '>')) == NULL) + continue; + if (right_bracket == left_bracket + 1) + continue; + for (end_of_name = left_bracket; end_of_name != buffer + && isspace(end_of_name[-1]); end_of_name--) + /* keep on looking */ + if (end_of_name == buffer) + continue; + name = xmalloc(end_of_name - buffer + 1); + strlcpy(name, buffer, end_of_name - buffer + 1); + email = xmalloc(right_bracket - left_bracket); + strlcpy(email, left_bracket + 1, right_bracket - left_bracket); + path_list_insert(email, &mailmap)->util = name; + } + fclose(f); + return 0; +} static int map_email(char *email, char *name, int maxlen) { @@ -269,6 +261,9 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) argc--; } + if (!access(".mailmap", R_OK)) + read_mailmap(".mailmap"); + if (rev.pending.nr == 1) die ("Need a range!"); else if (rev.pending.nr == 0) @@ -298,6 +293,8 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) list.strdup_paths = 1; path_list_clear(&list, 1); + mailmap.strdup_paths = 1; + path_list_clear(&mailmap, 1); return 0; } diff --git a/contrib/mailmap.linux b/contrib/mailmap.linux new file mode 100644 index 0000000..83927c9 --- /dev/null +++ b/contrib/mailmap.linux @@ -0,0 +1,40 @@ +# +# Even with git, we don't always have name translations. +# So have an email->real name table to translate the +# (hopefully few) missing names +# +Adrian Bunk +Andreas Herrmann +Andrew Morton +Andrew Vasquez +Christoph Hellwig +Corey Minyard +David Woodhouse +Domen Puncer +Douglas Gilbert +Ed L Cashin +Evgeniy Polyakov +Felix Moeller +Frank Zago +Greg Kroah-Hartman +James Bottomley +James Bottomley +Jeff Garzik +Jens Axboe +Kay Sievers +Mitesh shah +Morten Welinder +Morten Welinder +Morten Welinder +Morten Welinder +Nguyen Anh Quynh +Paolo 'Blaisorblade' Giarrusso +Peter A Jonsson +Ralf Wildenhues +Rudolf Marek +Rui Saraiva +Sachin P Sant +Santtu Hyrkk,Av(B +Simon Kelley +Tejun Heo +Tony Luck -- cgit v0.10.2-6-g49f6 From 549652361b7fea5a5e9046571c9f0bc4a7d5d6ef Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 19 Nov 2006 17:29:14 +0100 Subject: shortlog: handle email addresses case-insensitively Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/builtin-shortlog.c b/builtin-shortlog.c index afc9456..4775c11 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -34,6 +34,7 @@ static int read_mailmap(const char *filename) while (fgets(buffer, sizeof(buffer), f) != NULL) { char *end_of_name, *left_bracket, *right_bracket; char *name, *email; + int i; if (buffer[0] == '#') continue; if ((left_bracket = strchr(buffer, '<')) == NULL) @@ -50,7 +51,9 @@ static int read_mailmap(const char *filename) name = xmalloc(end_of_name - buffer + 1); strlcpy(name, buffer, end_of_name - buffer + 1); email = xmalloc(right_bracket - left_bracket); - strlcpy(email, left_bracket + 1, right_bracket - left_bracket); + for (i = 0; i < right_bracket - left_bracket - 1; i++) + email[i] = tolower(left_bracket[i + 1]); + email[right_bracket - left_bracket - 1] = '\0'; path_list_insert(email, &mailmap)->util = name; } fclose(f); @@ -68,6 +71,9 @@ static int map_email(char *email, char *name, int maxlen) return 0; *p = '\0'; + /* downcase the email address */ + for (p = email; *p; p++) + *p = tolower(*p); item = path_list_lookup(email, &mailmap); if (item != NULL) { const char *realname = (const char *)item->util; -- cgit v0.10.2-6-g49f6 From 6d6ab6104a5055b9f66cc9a80d55d2ef59d0763c Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 21 Nov 2006 21:12:06 +0100 Subject: shortlog: fix "-n" Since it is now a builtin optionally taking a range, we have to parse the options before the rev machinery, to be able to shadow the short hand "-n" for "--max-count". Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/builtin-shortlog.c b/builtin-shortlog.c index 4775c11..1456e1a 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -15,11 +15,11 @@ static int compare_by_number(const void *a1, const void *a2) const struct path_list *l1 = i1->util, *l2 = i2->util; if (l1->nr < l2->nr) - return -1; + return 1; else if (l1->nr == l2->nr) return 0; else - return +1; + return -1; } static struct path_list mailmap = {NULL, 0, 0, 0}; @@ -251,8 +251,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) struct path_list list = { NULL, 0, 0, 1 }; int i, j, sort_by_number = 0, summary = 0; - init_revisions(&rev, prefix); - argc = setup_revisions(argc, argv, &rev, NULL); + /* since -n is a shadowed rev argument, parse our args first */ while (argc > 1) { if (!strcmp(argv[1], "-n") || !strcmp(argv[1], "--numbered")) sort_by_number = 1; @@ -262,10 +261,14 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) usage(shortlog_usage); else - die ("unrecognized argument: %s", argv[1]); + break; argv++; argc--; } + init_revisions(&rev, prefix); + argc = setup_revisions(argc, argv, &rev, NULL); + if (argc > 1) + die ("unrecognized argument: %s", argv[1]); if (!access(".mailmap", R_OK)) read_mailmap(".mailmap"); @@ -278,7 +281,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) get_from_rev(&rev, &list); if (sort_by_number) - qsort(list.items, sizeof(struct path_list_item), list.nr, + qsort(list.items, list.nr, sizeof(struct path_list_item), compare_by_number); for (i = 0; i < list.nr; i++) { -- cgit v0.10.2-6-g49f6 From ac60c94d74ff3341a5175ca865fd52a0a0189146 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Tue, 21 Nov 2006 15:49:45 -0500 Subject: builtin git-shortlog is broken Another small patch to fix the output result to be conform with the perl version. Signed-off-by: Nicolas Pitre Acked-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/builtin-shortlog.c b/builtin-shortlog.c index 1456e1a..b760b47 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -7,7 +7,7 @@ #include static const char shortlog_usage[] = -"git-shortlog [-n] [-s] [... ]\n"; +"git-shortlog [-n] [-s] [... ]"; static int compare_by_number(const void *a1, const void *a2) { @@ -287,8 +287,10 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) for (i = 0; i < list.nr; i++) { struct path_list *onelines = list.items[i].util; - printf("%s (%d):\n", list.items[i].path, onelines->nr); - if (!summary) { + if (summary) { + printf("%s: %d\n", list.items[i].path, onelines->nr); + } else { + printf("%s (%d):\n", list.items[i].path, onelines->nr); for (j = onelines->nr - 1; j >= 0; j--) printf(" %s\n", onelines->items[j].path); printf("\n"); -- cgit v0.10.2-6-g49f6 From 7cdbff14d4823c3a3d64c2011ab0b23f794efef8 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 20 Nov 2006 00:49:31 -0800 Subject: remove merge-recursive-old This frees the Porcelain-ish that comes with the core Python-free. Signed-off-by: Junio C Hamano diff --git a/.gitignore b/.gitignore index 4c8c8e4..7f2cd55 100644 --- a/.gitignore +++ b/.gitignore @@ -66,7 +66,6 @@ git-merge-one-file git-merge-ours git-merge-recur git-merge-recursive -git-merge-recursive-old git-merge-resolve git-merge-stupid git-mktag diff --git a/INSTALL b/INSTALL index fce6bc3..8f69039 100644 --- a/INSTALL +++ b/INSTALL @@ -99,9 +99,6 @@ Issues of note: - "perl" and POSIX-compliant shells are needed to use most of the barebone Porcelainish scripts. - - "python" 2.3 or more recent; if you have 2.3, you may need - to build with "make WITH_OWN_SUBPROCESS_PY=YesPlease". - - Some platform specific issues are dealt with Makefile rules, but depending on your specific installation, you may not have all the libraries/tools needed, or you may have diff --git a/Makefile b/Makefile index 36ce8cd..fc30dcb 100644 --- a/Makefile +++ b/Makefile @@ -69,8 +69,6 @@ all: # # Define NO_MMAP if you want to avoid mmap. # -# Define WITH_OWN_SUBPROCESS_PY if you want to use with python 2.3. -# # Define NO_IPV6 if you lack IPv6 support and getaddrinfo(). # # Define NO_SOCKADDR_STORAGE if your platform does not have struct @@ -116,7 +114,6 @@ prefix = $(HOME) bindir = $(prefix)/bin gitexecdir = $(bindir) template_dir = $(prefix)/share/git-core/templates/ -GIT_PYTHON_DIR = $(prefix)/share/git-core/python # DESTDIR= # default configuration for gitweb @@ -135,7 +132,7 @@ GITWEB_FAVICON = git-favicon.png GITWEB_SITE_HEADER = GITWEB_SITE_FOOTER = -export prefix bindir gitexecdir template_dir GIT_PYTHON_DIR +export prefix bindir gitexecdir template_dir CC = gcc AR = ar @@ -179,12 +176,8 @@ SCRIPT_PERL = \ git-svnimport.perl git-cvsexportcommit.perl \ git-send-email.perl git-svn.perl -SCRIPT_PYTHON = \ - git-merge-recursive-old.py - SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ $(patsubst %.perl,%,$(SCRIPT_PERL)) \ - $(patsubst %.py,%,$(SCRIPT_PYTHON)) \ git-cherry-pick git-status git-instaweb # ... and all the rest that could be moved out of bindir to gitexecdir @@ -227,12 +220,6 @@ endif ifndef PERL_PATH PERL_PATH = /usr/bin/perl endif -ifndef PYTHON_PATH - PYTHON_PATH = /usr/bin/python -endif - -PYMODULES = \ - gitMergeCommon.py LIB_FILE=libgit.a XDIFF_LIB=xdiff/lib.a @@ -423,16 +410,6 @@ endif -include config.mak.autogen -include config.mak -ifdef WITH_OWN_SUBPROCESS_PY - PYMODULES += compat/subprocess.py -else - ifeq ($(NO_PYTHON),) - ifneq ($(shell $(PYTHON_PATH) -c 'import subprocess;print"OK"' 2>/dev/null),OK) - PYMODULES += compat/subprocess.py - endif - endif -endif - ifndef NO_CURL ifdef CURLDIR # This is still problematic -- gcc does not always want -R. @@ -574,8 +551,6 @@ prefix_SQ = $(subst ','\'',$(prefix)) SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) -PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH)) -GIT_PYTHON_DIR_SQ = $(subst ','\'',$(GIT_PYTHON_DIR)) LIBS = $(GITLIBS) $(EXTLIBS) @@ -622,7 +597,6 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ -e 's/@@NO_CURL@@/$(NO_CURL)/g' \ - -e 's/@@NO_PYTHON@@/$(NO_PYTHON)/g' \ $@.sh >$@+ chmod +x $@+ mv $@+ $@ @@ -644,15 +618,6 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl chmod +x $@+ mv $@+ $@ -$(patsubst %.py,%,$(SCRIPT_PYTHON)) : % : %.py GIT-CFLAGS - rm -f $@ $@+ - sed -e '1s|#!.*python|#!$(PYTHON_PATH_SQ)|' \ - -e 's|@@GIT_PYTHON_PATH@@|$(GIT_PYTHON_DIR_SQ)|g' \ - -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ - $@.py >$@+ - chmod +x $@+ - mv $@+ $@ - git-cherry-pick: git-revert cp $< $@+ mv $@+ $@ @@ -689,7 +654,6 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ -e 's/@@NO_CURL@@/$(NO_CURL)/g' \ - -e 's/@@NO_PYTHON@@/$(NO_PYTHON)/g' \ -e '/@@GITWEB_CGI@@/r gitweb/gitweb.cgi' \ -e '/@@GITWEB_CGI@@/d' \ -e '/@@GITWEB_CSS@@/r gitweb/gitweb.css' \ @@ -709,7 +673,6 @@ configure: configure.ac git$X git.spec \ $(patsubst %.sh,%,$(SCRIPT_SH)) \ $(patsubst %.perl,%,$(SCRIPT_PERL)) \ - $(patsubst %.py,%,$(SCRIPT_PYTHON)) \ : GIT-VERSION-FILE %.o: %.c GIT-CFLAGS @@ -783,7 +746,7 @@ tags: find . -name '*.[hcS]' -print | xargs ctags -a ### Detect prefix changes -TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):$(GIT_PYTHON_DIR_SQ):\ +TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):\ $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ) GIT-CFLAGS: .FORCE-GIT-CFLAGS @@ -799,7 +762,6 @@ GIT-CFLAGS: .FORCE-GIT-CFLAGS # However, the environment gets quite big, and some programs have problems # with that. -export NO_PYTHON export NO_SVN_TESTS test: all @@ -834,8 +796,6 @@ install: all $(INSTALL) git$X gitk '$(DESTDIR_SQ)$(bindir_SQ)' $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install $(MAKE) -C perl install - $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)' - $(INSTALL) $(PYMODULES) '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)' if test 'z$(bindir_SQ)' != 'z$(gitexecdir_SQ)'; \ then \ ln -f '$(DESTDIR_SQ)$(bindir_SQ)/git$X' \ @@ -922,7 +882,6 @@ check-docs:: case "$$v" in \ git-merge-octopus | git-merge-ours | git-merge-recursive | \ git-merge-resolve | git-merge-stupid | git-merge-recur | \ - git-merge-recursive-old | \ git-ssh-pull | git-ssh-push ) continue ;; \ esac ; \ test -f "Documentation/$$v.txt" || \ diff --git a/compat/subprocess.py b/compat/subprocess.py deleted file mode 100644 index 6474eab..0000000 --- a/compat/subprocess.py +++ /dev/null @@ -1,1149 +0,0 @@ -# subprocess - Subprocesses with accessible I/O streams -# -# For more information about this module, see PEP 324. -# -# This module should remain compatible with Python 2.2, see PEP 291. -# -# Copyright (c) 2003-2005 by Peter Astrand -# -# Licensed to PSF under a Contributor Agreement. -# See http://www.python.org/2.4/license for licensing details. - -r"""subprocess - Subprocesses with accessible I/O streams - -This module allows you to spawn processes, connect to their -input/output/error pipes, and obtain their return codes. This module -intends to replace several other, older modules and functions, like: - -os.system -os.spawn* -os.popen* -popen2.* -commands.* - -Information about how the subprocess module can be used to replace these -modules and functions can be found below. - - - -Using the subprocess module -=========================== -This module defines one class called Popen: - -class Popen(args, bufsize=0, executable=None, - stdin=None, stdout=None, stderr=None, - preexec_fn=None, close_fds=False, shell=False, - cwd=None, env=None, universal_newlines=False, - startupinfo=None, creationflags=0): - - -Arguments are: - -args should be a string, or a sequence of program arguments. The -program to execute is normally the first item in the args sequence or -string, but can be explicitly set by using the executable argument. - -On UNIX, with shell=False (default): In this case, the Popen class -uses os.execvp() to execute the child program. args should normally -be a sequence. A string will be treated as a sequence with the string -as the only item (the program to execute). - -On UNIX, with shell=True: If args is a string, it specifies the -command string to execute through the shell. If args is a sequence, -the first item specifies the command string, and any additional items -will be treated as additional shell arguments. - -On Windows: the Popen class uses CreateProcess() to execute the child -program, which operates on strings. If args is a sequence, it will be -converted to a string using the list2cmdline method. Please note that -not all MS Windows applications interpret the command line the same -way: The list2cmdline is designed for applications using the same -rules as the MS C runtime. - -bufsize, if given, has the same meaning as the corresponding argument -to the built-in open() function: 0 means unbuffered, 1 means line -buffered, any other positive value means use a buffer of -(approximately) that size. A negative bufsize means to use the system -default, which usually means fully buffered. The default value for -bufsize is 0 (unbuffered). - -stdin, stdout and stderr specify the executed programs' standard -input, standard output and standard error file handles, respectively. -Valid values are PIPE, an existing file descriptor (a positive -integer), an existing file object, and None. PIPE indicates that a -new pipe to the child should be created. With None, no redirection -will occur; the child's file handles will be inherited from the -parent. Additionally, stderr can be STDOUT, which indicates that the -stderr data from the applications should be captured into the same -file handle as for stdout. - -If preexec_fn is set to a callable object, this object will be called -in the child process just before the child is executed. - -If close_fds is true, all file descriptors except 0, 1 and 2 will be -closed before the child process is executed. - -if shell is true, the specified command will be executed through the -shell. - -If cwd is not None, the current directory will be changed to cwd -before the child is executed. - -If env is not None, it defines the environment variables for the new -process. - -If universal_newlines is true, the file objects stdout and stderr are -opened as a text files, but lines may be terminated by any of '\n', -the Unix end-of-line convention, '\r', the Macintosh convention or -'\r\n', the Windows convention. All of these external representations -are seen as '\n' by the Python program. Note: This feature is only -available if Python is built with universal newline support (the -default). Also, the newlines attribute of the file objects stdout, -stdin and stderr are not updated by the communicate() method. - -The startupinfo and creationflags, if given, will be passed to the -underlying CreateProcess() function. They can specify things such as -appearance of the main window and priority for the new process. -(Windows only) - - -This module also defines two shortcut functions: - -call(*args, **kwargs): - Run command with arguments. Wait for command to complete, then - return the returncode attribute. - - The arguments are the same as for the Popen constructor. Example: - - retcode = call(["ls", "-l"]) - - -Exceptions ----------- -Exceptions raised in the child process, before the new program has -started to execute, will be re-raised in the parent. Additionally, -the exception object will have one extra attribute called -'child_traceback', which is a string containing traceback information -from the childs point of view. - -The most common exception raised is OSError. This occurs, for -example, when trying to execute a non-existent file. Applications -should prepare for OSErrors. - -A ValueError will be raised if Popen is called with invalid arguments. - - -Security --------- -Unlike some other popen functions, this implementation will never call -/bin/sh implicitly. This means that all characters, including shell -metacharacters, can safely be passed to child processes. - - -Popen objects -============= -Instances of the Popen class have the following methods: - -poll() - Check if child process has terminated. Returns returncode - attribute. - -wait() - Wait for child process to terminate. Returns returncode attribute. - -communicate(input=None) - Interact with process: Send data to stdin. Read data from stdout - and stderr, until end-of-file is reached. Wait for process to - terminate. The optional stdin argument should be a string to be - sent to the child process, or None, if no data should be sent to - the child. - - communicate() returns a tuple (stdout, stderr). - - Note: The data read is buffered in memory, so do not use this - method if the data size is large or unlimited. - -The following attributes are also available: - -stdin - If the stdin argument is PIPE, this attribute is a file object - that provides input to the child process. Otherwise, it is None. - -stdout - If the stdout argument is PIPE, this attribute is a file object - that provides output from the child process. Otherwise, it is - None. - -stderr - If the stderr argument is PIPE, this attribute is file object that - provides error output from the child process. Otherwise, it is - None. - -pid - The process ID of the child process. - -returncode - The child return code. A None value indicates that the process - hasn't terminated yet. A negative value -N indicates that the - child was terminated by signal N (UNIX only). - - -Replacing older functions with the subprocess module -==================================================== -In this section, "a ==> b" means that b can be used as a replacement -for a. - -Note: All functions in this section fail (more or less) silently if -the executed program cannot be found; this module raises an OSError -exception. - -In the following examples, we assume that the subprocess module is -imported with "from subprocess import *". - - -Replacing /bin/sh shell backquote ---------------------------------- -output=`mycmd myarg` -==> -output = Popen(["mycmd", "myarg"], stdout=PIPE).communicate()[0] - - -Replacing shell pipe line -------------------------- -output=`dmesg | grep hda` -==> -p1 = Popen(["dmesg"], stdout=PIPE) -p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE) -output = p2.communicate()[0] - - -Replacing os.system() ---------------------- -sts = os.system("mycmd" + " myarg") -==> -p = Popen("mycmd" + " myarg", shell=True) -sts = os.waitpid(p.pid, 0) - -Note: - -* Calling the program through the shell is usually not required. - -* It's easier to look at the returncode attribute than the - exitstatus. - -A more real-world example would look like this: - -try: - retcode = call("mycmd" + " myarg", shell=True) - if retcode < 0: - print >>sys.stderr, "Child was terminated by signal", -retcode - else: - print >>sys.stderr, "Child returned", retcode -except OSError, e: - print >>sys.stderr, "Execution failed:", e - - -Replacing os.spawn* -------------------- -P_NOWAIT example: - -pid = os.spawnlp(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg") -==> -pid = Popen(["/bin/mycmd", "myarg"]).pid - - -P_WAIT example: - -retcode = os.spawnlp(os.P_WAIT, "/bin/mycmd", "mycmd", "myarg") -==> -retcode = call(["/bin/mycmd", "myarg"]) - - -Vector example: - -os.spawnvp(os.P_NOWAIT, path, args) -==> -Popen([path] + args[1:]) - - -Environment example: - -os.spawnlpe(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg", env) -==> -Popen(["/bin/mycmd", "myarg"], env={"PATH": "/usr/bin"}) - - -Replacing os.popen* -------------------- -pipe = os.popen(cmd, mode='r', bufsize) -==> -pipe = Popen(cmd, shell=True, bufsize=bufsize, stdout=PIPE).stdout - -pipe = os.popen(cmd, mode='w', bufsize) -==> -pipe = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE).stdin - - -(child_stdin, child_stdout) = os.popen2(cmd, mode, bufsize) -==> -p = Popen(cmd, shell=True, bufsize=bufsize, - stdin=PIPE, stdout=PIPE, close_fds=True) -(child_stdin, child_stdout) = (p.stdin, p.stdout) - - -(child_stdin, - child_stdout, - child_stderr) = os.popen3(cmd, mode, bufsize) -==> -p = Popen(cmd, shell=True, bufsize=bufsize, - stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) -(child_stdin, - child_stdout, - child_stderr) = (p.stdin, p.stdout, p.stderr) - - -(child_stdin, child_stdout_and_stderr) = os.popen4(cmd, mode, bufsize) -==> -p = Popen(cmd, shell=True, bufsize=bufsize, - stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True) -(child_stdin, child_stdout_and_stderr) = (p.stdin, p.stdout) - - -Replacing popen2.* ------------------- -Note: If the cmd argument to popen2 functions is a string, the command -is executed through /bin/sh. If it is a list, the command is directly -executed. - -(child_stdout, child_stdin) = popen2.popen2("somestring", bufsize, mode) -==> -p = Popen(["somestring"], shell=True, bufsize=bufsize - stdin=PIPE, stdout=PIPE, close_fds=True) -(child_stdout, child_stdin) = (p.stdout, p.stdin) - - -(child_stdout, child_stdin) = popen2.popen2(["mycmd", "myarg"], bufsize, mode) -==> -p = Popen(["mycmd", "myarg"], bufsize=bufsize, - stdin=PIPE, stdout=PIPE, close_fds=True) -(child_stdout, child_stdin) = (p.stdout, p.stdin) - -The popen2.Popen3 and popen3.Popen4 basically works as subprocess.Popen, -except that: - -* subprocess.Popen raises an exception if the execution fails -* the capturestderr argument is replaced with the stderr argument. -* stdin=PIPE and stdout=PIPE must be specified. -* popen2 closes all filedescriptors by default, but you have to specify - close_fds=True with subprocess.Popen. - - -""" - -import sys -mswindows = (sys.platform == "win32") - -import os -import types -import traceback - -if mswindows: - import threading - import msvcrt - if 0: # <-- change this to use pywin32 instead of the _subprocess driver - import pywintypes - from win32api import GetStdHandle, STD_INPUT_HANDLE, \ - STD_OUTPUT_HANDLE, STD_ERROR_HANDLE - from win32api import GetCurrentProcess, DuplicateHandle, \ - GetModuleFileName, GetVersion - from win32con import DUPLICATE_SAME_ACCESS, SW_HIDE - from win32pipe import CreatePipe - from win32process import CreateProcess, STARTUPINFO, \ - GetExitCodeProcess, STARTF_USESTDHANDLES, \ - STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE - from win32event import WaitForSingleObject, INFINITE, WAIT_OBJECT_0 - else: - from _subprocess import * - class STARTUPINFO: - dwFlags = 0 - hStdInput = None - hStdOutput = None - hStdError = None - class pywintypes: - error = IOError -else: - import select - import errno - import fcntl - import pickle - -__all__ = ["Popen", "PIPE", "STDOUT", "call"] - -try: - MAXFD = os.sysconf("SC_OPEN_MAX") -except: - MAXFD = 256 - -# True/False does not exist on 2.2.0 -try: - False -except NameError: - False = 0 - True = 1 - -_active = [] - -def _cleanup(): - for inst in _active[:]: - inst.poll() - -PIPE = -1 -STDOUT = -2 - - -def call(*args, **kwargs): - """Run command with arguments. Wait for command to complete, then - return the returncode attribute. - - The arguments are the same as for the Popen constructor. Example: - - retcode = call(["ls", "-l"]) - """ - return Popen(*args, **kwargs).wait() - - -def list2cmdline(seq): - """ - Translate a sequence of arguments into a command line - string, using the same rules as the MS C runtime: - - 1) Arguments are delimited by white space, which is either a - space or a tab. - - 2) A string surrounded by double quotation marks is - interpreted as a single argument, regardless of white space - contained within. A quoted string can be embedded in an - argument. - - 3) A double quotation mark preceded by a backslash is - interpreted as a literal double quotation mark. - - 4) Backslashes are interpreted literally, unless they - immediately precede a double quotation mark. - - 5) If backslashes immediately precede a double quotation mark, - every pair of backslashes is interpreted as a literal - backslash. If the number of backslashes is odd, the last - backslash escapes the next double quotation mark as - described in rule 3. - """ - - # See - # http://msdn.microsoft.com/library/en-us/vccelng/htm/progs_12.asp - result = [] - needquote = False - for arg in seq: - bs_buf = [] - - # Add a space to separate this argument from the others - if result: - result.append(' ') - - needquote = (" " in arg) or ("\t" in arg) - if needquote: - result.append('"') - - for c in arg: - if c == '\\': - # Don't know if we need to double yet. - bs_buf.append(c) - elif c == '"': - # Double backspaces. - result.append('\\' * len(bs_buf)*2) - bs_buf = [] - result.append('\\"') - else: - # Normal char - if bs_buf: - result.extend(bs_buf) - bs_buf = [] - result.append(c) - - # Add remaining backspaces, if any. - if bs_buf: - result.extend(bs_buf) - - if needquote: - result.extend(bs_buf) - result.append('"') - - return ''.join(result) - - -class Popen(object): - def __init__(self, args, bufsize=0, executable=None, - stdin=None, stdout=None, stderr=None, - preexec_fn=None, close_fds=False, shell=False, - cwd=None, env=None, universal_newlines=False, - startupinfo=None, creationflags=0): - """Create new Popen instance.""" - _cleanup() - - if not isinstance(bufsize, (int, long)): - raise TypeError("bufsize must be an integer") - - if mswindows: - if preexec_fn is not None: - raise ValueError("preexec_fn is not supported on Windows " - "platforms") - if close_fds: - raise ValueError("close_fds is not supported on Windows " - "platforms") - else: - # POSIX - if startupinfo is not None: - raise ValueError("startupinfo is only supported on Windows " - "platforms") - if creationflags != 0: - raise ValueError("creationflags is only supported on Windows " - "platforms") - - self.stdin = None - self.stdout = None - self.stderr = None - self.pid = None - self.returncode = None - self.universal_newlines = universal_newlines - - # Input and output objects. The general principle is like - # this: - # - # Parent Child - # ------ ----- - # p2cwrite ---stdin---> p2cread - # c2pread <--stdout--- c2pwrite - # errread <--stderr--- errwrite - # - # On POSIX, the child objects are file descriptors. On - # Windows, these are Windows file handles. The parent objects - # are file descriptors on both platforms. The parent objects - # are None when not using PIPEs. The child objects are None - # when not redirecting. - - (p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite) = self._get_handles(stdin, stdout, stderr) - - self._execute_child(args, executable, preexec_fn, close_fds, - cwd, env, universal_newlines, - startupinfo, creationflags, shell, - p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite) - - if p2cwrite: - self.stdin = os.fdopen(p2cwrite, 'wb', bufsize) - if c2pread: - if universal_newlines: - self.stdout = os.fdopen(c2pread, 'rU', bufsize) - else: - self.stdout = os.fdopen(c2pread, 'rb', bufsize) - if errread: - if universal_newlines: - self.stderr = os.fdopen(errread, 'rU', bufsize) - else: - self.stderr = os.fdopen(errread, 'rb', bufsize) - - _active.append(self) - - - def _translate_newlines(self, data): - data = data.replace("\r\n", "\n") - data = data.replace("\r", "\n") - return data - - - if mswindows: - # - # Windows methods - # - def _get_handles(self, stdin, stdout, stderr): - """Construct and return tuple with IO objects: - p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite - """ - if stdin == None and stdout == None and stderr == None: - return (None, None, None, None, None, None) - - p2cread, p2cwrite = None, None - c2pread, c2pwrite = None, None - errread, errwrite = None, None - - if stdin == None: - p2cread = GetStdHandle(STD_INPUT_HANDLE) - elif stdin == PIPE: - p2cread, p2cwrite = CreatePipe(None, 0) - # Detach and turn into fd - p2cwrite = p2cwrite.Detach() - p2cwrite = msvcrt.open_osfhandle(p2cwrite, 0) - elif type(stdin) == types.IntType: - p2cread = msvcrt.get_osfhandle(stdin) - else: - # Assuming file-like object - p2cread = msvcrt.get_osfhandle(stdin.fileno()) - p2cread = self._make_inheritable(p2cread) - - if stdout == None: - c2pwrite = GetStdHandle(STD_OUTPUT_HANDLE) - elif stdout == PIPE: - c2pread, c2pwrite = CreatePipe(None, 0) - # Detach and turn into fd - c2pread = c2pread.Detach() - c2pread = msvcrt.open_osfhandle(c2pread, 0) - elif type(stdout) == types.IntType: - c2pwrite = msvcrt.get_osfhandle(stdout) - else: - # Assuming file-like object - c2pwrite = msvcrt.get_osfhandle(stdout.fileno()) - c2pwrite = self._make_inheritable(c2pwrite) - - if stderr == None: - errwrite = GetStdHandle(STD_ERROR_HANDLE) - elif stderr == PIPE: - errread, errwrite = CreatePipe(None, 0) - # Detach and turn into fd - errread = errread.Detach() - errread = msvcrt.open_osfhandle(errread, 0) - elif stderr == STDOUT: - errwrite = c2pwrite - elif type(stderr) == types.IntType: - errwrite = msvcrt.get_osfhandle(stderr) - else: - # Assuming file-like object - errwrite = msvcrt.get_osfhandle(stderr.fileno()) - errwrite = self._make_inheritable(errwrite) - - return (p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite) - - - def _make_inheritable(self, handle): - """Return a duplicate of handle, which is inheritable""" - return DuplicateHandle(GetCurrentProcess(), handle, - GetCurrentProcess(), 0, 1, - DUPLICATE_SAME_ACCESS) - - - def _find_w9xpopen(self): - """Find and return absolute path to w9xpopen.exe""" - w9xpopen = os.path.join(os.path.dirname(GetModuleFileName(0)), - "w9xpopen.exe") - if not os.path.exists(w9xpopen): - # Eeek - file-not-found - possibly an embedding - # situation - see if we can locate it in sys.exec_prefix - w9xpopen = os.path.join(os.path.dirname(sys.exec_prefix), - "w9xpopen.exe") - if not os.path.exists(w9xpopen): - raise RuntimeError("Cannot locate w9xpopen.exe, which is " - "needed for Popen to work with your " - "shell or platform.") - return w9xpopen - - - def _execute_child(self, args, executable, preexec_fn, close_fds, - cwd, env, universal_newlines, - startupinfo, creationflags, shell, - p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite): - """Execute program (MS Windows version)""" - - if not isinstance(args, types.StringTypes): - args = list2cmdline(args) - - # Process startup details - default_startupinfo = STARTUPINFO() - if startupinfo == None: - startupinfo = default_startupinfo - if not None in (p2cread, c2pwrite, errwrite): - startupinfo.dwFlags |= STARTF_USESTDHANDLES - startupinfo.hStdInput = p2cread - startupinfo.hStdOutput = c2pwrite - startupinfo.hStdError = errwrite - - if shell: - default_startupinfo.dwFlags |= STARTF_USESHOWWINDOW - default_startupinfo.wShowWindow = SW_HIDE - comspec = os.environ.get("COMSPEC", "cmd.exe") - args = comspec + " /c " + args - if (GetVersion() >= 0x80000000L or - os.path.basename(comspec).lower() == "command.com"): - # Win9x, or using command.com on NT. We need to - # use the w9xpopen intermediate program. For more - # information, see KB Q150956 - # (http://web.archive.org/web/20011105084002/http://support.microsoft.com/support/kb/articles/Q150/9/56.asp) - w9xpopen = self._find_w9xpopen() - args = '"%s" %s' % (w9xpopen, args) - # Not passing CREATE_NEW_CONSOLE has been known to - # cause random failures on win9x. Specifically a - # dialog: "Your program accessed mem currently in - # use at xxx" and a hopeful warning about the - # stability of your system. Cost is Ctrl+C wont - # kill children. - creationflags |= CREATE_NEW_CONSOLE - - # Start the process - try: - hp, ht, pid, tid = CreateProcess(executable, args, - # no special security - None, None, - # must inherit handles to pass std - # handles - 1, - creationflags, - env, - cwd, - startupinfo) - except pywintypes.error, e: - # Translate pywintypes.error to WindowsError, which is - # a subclass of OSError. FIXME: We should really - # translate errno using _sys_errlist (or simliar), but - # how can this be done from Python? - raise WindowsError(*e.args) - - # Retain the process handle, but close the thread handle - self._handle = hp - self.pid = pid - ht.Close() - - # Child is launched. Close the parent's copy of those pipe - # handles that only the child should have open. You need - # to make sure that no handles to the write end of the - # output pipe are maintained in this process or else the - # pipe will not close when the child process exits and the - # ReadFile will hang. - if p2cread != None: - p2cread.Close() - if c2pwrite != None: - c2pwrite.Close() - if errwrite != None: - errwrite.Close() - - - def poll(self): - """Check if child process has terminated. Returns returncode - attribute.""" - if self.returncode == None: - if WaitForSingleObject(self._handle, 0) == WAIT_OBJECT_0: - self.returncode = GetExitCodeProcess(self._handle) - _active.remove(self) - return self.returncode - - - def wait(self): - """Wait for child process to terminate. Returns returncode - attribute.""" - if self.returncode == None: - obj = WaitForSingleObject(self._handle, INFINITE) - self.returncode = GetExitCodeProcess(self._handle) - _active.remove(self) - return self.returncode - - - def _readerthread(self, fh, buffer): - buffer.append(fh.read()) - - - def communicate(self, input=None): - """Interact with process: Send data to stdin. Read data from - stdout and stderr, until end-of-file is reached. Wait for - process to terminate. The optional input argument should be a - string to be sent to the child process, or None, if no data - should be sent to the child. - - communicate() returns a tuple (stdout, stderr).""" - stdout = None # Return - stderr = None # Return - - if self.stdout: - stdout = [] - stdout_thread = threading.Thread(target=self._readerthread, - args=(self.stdout, stdout)) - stdout_thread.setDaemon(True) - stdout_thread.start() - if self.stderr: - stderr = [] - stderr_thread = threading.Thread(target=self._readerthread, - args=(self.stderr, stderr)) - stderr_thread.setDaemon(True) - stderr_thread.start() - - if self.stdin: - if input != None: - self.stdin.write(input) - self.stdin.close() - - if self.stdout: - stdout_thread.join() - if self.stderr: - stderr_thread.join() - - # All data exchanged. Translate lists into strings. - if stdout != None: - stdout = stdout[0] - if stderr != None: - stderr = stderr[0] - - # Translate newlines, if requested. We cannot let the file - # object do the translation: It is based on stdio, which is - # impossible to combine with select (unless forcing no - # buffering). - if self.universal_newlines and hasattr(open, 'newlines'): - if stdout: - stdout = self._translate_newlines(stdout) - if stderr: - stderr = self._translate_newlines(stderr) - - self.wait() - return (stdout, stderr) - - else: - # - # POSIX methods - # - def _get_handles(self, stdin, stdout, stderr): - """Construct and return tuple with IO objects: - p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite - """ - p2cread, p2cwrite = None, None - c2pread, c2pwrite = None, None - errread, errwrite = None, None - - if stdin == None: - pass - elif stdin == PIPE: - p2cread, p2cwrite = os.pipe() - elif type(stdin) == types.IntType: - p2cread = stdin - else: - # Assuming file-like object - p2cread = stdin.fileno() - - if stdout == None: - pass - elif stdout == PIPE: - c2pread, c2pwrite = os.pipe() - elif type(stdout) == types.IntType: - c2pwrite = stdout - else: - # Assuming file-like object - c2pwrite = stdout.fileno() - - if stderr == None: - pass - elif stderr == PIPE: - errread, errwrite = os.pipe() - elif stderr == STDOUT: - errwrite = c2pwrite - elif type(stderr) == types.IntType: - errwrite = stderr - else: - # Assuming file-like object - errwrite = stderr.fileno() - - return (p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite) - - - def _set_cloexec_flag(self, fd): - try: - cloexec_flag = fcntl.FD_CLOEXEC - except AttributeError: - cloexec_flag = 1 - - old = fcntl.fcntl(fd, fcntl.F_GETFD) - fcntl.fcntl(fd, fcntl.F_SETFD, old | cloexec_flag) - - - def _close_fds(self, but): - for i in range(3, MAXFD): - if i == but: - continue - try: - os.close(i) - except: - pass - - - def _execute_child(self, args, executable, preexec_fn, close_fds, - cwd, env, universal_newlines, - startupinfo, creationflags, shell, - p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite): - """Execute program (POSIX version)""" - - if isinstance(args, types.StringTypes): - args = [args] - - if shell: - args = ["/bin/sh", "-c"] + args - - if executable == None: - executable = args[0] - - # For transferring possible exec failure from child to parent - # The first char specifies the exception type: 0 means - # OSError, 1 means some other error. - errpipe_read, errpipe_write = os.pipe() - self._set_cloexec_flag(errpipe_write) - - self.pid = os.fork() - if self.pid == 0: - # Child - try: - # Close parent's pipe ends - if p2cwrite: - os.close(p2cwrite) - if c2pread: - os.close(c2pread) - if errread: - os.close(errread) - os.close(errpipe_read) - - # Dup fds for child - if p2cread: - os.dup2(p2cread, 0) - if c2pwrite: - os.dup2(c2pwrite, 1) - if errwrite: - os.dup2(errwrite, 2) - - # Close pipe fds. Make sure we doesn't close the same - # fd more than once. - if p2cread: - os.close(p2cread) - if c2pwrite and c2pwrite not in (p2cread,): - os.close(c2pwrite) - if errwrite and errwrite not in (p2cread, c2pwrite): - os.close(errwrite) - - # Close all other fds, if asked for - if close_fds: - self._close_fds(but=errpipe_write) - - if cwd != None: - os.chdir(cwd) - - if preexec_fn: - apply(preexec_fn) - - if env == None: - os.execvp(executable, args) - else: - os.execvpe(executable, args, env) - - except: - exc_type, exc_value, tb = sys.exc_info() - # Save the traceback and attach it to the exception object - exc_lines = traceback.format_exception(exc_type, - exc_value, - tb) - exc_value.child_traceback = ''.join(exc_lines) - os.write(errpipe_write, pickle.dumps(exc_value)) - - # This exitcode won't be reported to applications, so it - # really doesn't matter what we return. - os._exit(255) - - # Parent - os.close(errpipe_write) - if p2cread and p2cwrite: - os.close(p2cread) - if c2pwrite and c2pread: - os.close(c2pwrite) - if errwrite and errread: - os.close(errwrite) - - # Wait for exec to fail or succeed; possibly raising exception - data = os.read(errpipe_read, 1048576) # Exceptions limited to 1 MB - os.close(errpipe_read) - if data != "": - os.waitpid(self.pid, 0) - child_exception = pickle.loads(data) - raise child_exception - - - def _handle_exitstatus(self, sts): - if os.WIFSIGNALED(sts): - self.returncode = -os.WTERMSIG(sts) - elif os.WIFEXITED(sts): - self.returncode = os.WEXITSTATUS(sts) - else: - # Should never happen - raise RuntimeError("Unknown child exit status!") - - _active.remove(self) - - - def poll(self): - """Check if child process has terminated. Returns returncode - attribute.""" - if self.returncode == None: - try: - pid, sts = os.waitpid(self.pid, os.WNOHANG) - if pid == self.pid: - self._handle_exitstatus(sts) - except os.error: - pass - return self.returncode - - - def wait(self): - """Wait for child process to terminate. Returns returncode - attribute.""" - if self.returncode == None: - pid, sts = os.waitpid(self.pid, 0) - self._handle_exitstatus(sts) - return self.returncode - - - def communicate(self, input=None): - """Interact with process: Send data to stdin. Read data from - stdout and stderr, until end-of-file is reached. Wait for - process to terminate. The optional input argument should be a - string to be sent to the child process, or None, if no data - should be sent to the child. - - communicate() returns a tuple (stdout, stderr).""" - read_set = [] - write_set = [] - stdout = None # Return - stderr = None # Return - - if self.stdin: - # Flush stdio buffer. This might block, if the user has - # been writing to .stdin in an uncontrolled fashion. - self.stdin.flush() - if input: - write_set.append(self.stdin) - else: - self.stdin.close() - if self.stdout: - read_set.append(self.stdout) - stdout = [] - if self.stderr: - read_set.append(self.stderr) - stderr = [] - - while read_set or write_set: - rlist, wlist, xlist = select.select(read_set, write_set, []) - - if self.stdin in wlist: - # When select has indicated that the file is writable, - # we can write up to PIPE_BUF bytes without risk - # blocking. POSIX defines PIPE_BUF >= 512 - bytes_written = os.write(self.stdin.fileno(), input[:512]) - input = input[bytes_written:] - if not input: - self.stdin.close() - write_set.remove(self.stdin) - - if self.stdout in rlist: - data = os.read(self.stdout.fileno(), 1024) - if data == "": - self.stdout.close() - read_set.remove(self.stdout) - stdout.append(data) - - if self.stderr in rlist: - data = os.read(self.stderr.fileno(), 1024) - if data == "": - self.stderr.close() - read_set.remove(self.stderr) - stderr.append(data) - - # All data exchanged. Translate lists into strings. - if stdout != None: - stdout = ''.join(stdout) - if stderr != None: - stderr = ''.join(stderr) - - # Translate newlines, if requested. We cannot let the file - # object do the translation: It is based on stdio, which is - # impossible to combine with select (unless forcing no - # buffering). - if self.universal_newlines and hasattr(open, 'newlines'): - if stdout: - stdout = self._translate_newlines(stdout) - if stderr: - stderr = self._translate_newlines(stderr) - - self.wait() - return (stdout, stderr) - - -def _demo_posix(): - # - # Example 1: Simple redirection: Get process list - # - plist = Popen(["ps"], stdout=PIPE).communicate()[0] - print "Process list:" - print plist - - # - # Example 2: Change uid before executing child - # - if os.getuid() == 0: - p = Popen(["id"], preexec_fn=lambda: os.setuid(100)) - p.wait() - - # - # Example 3: Connecting several subprocesses - # - print "Looking for 'hda'..." - p1 = Popen(["dmesg"], stdout=PIPE) - p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE) - print repr(p2.communicate()[0]) - - # - # Example 4: Catch execution error - # - print - print "Trying a weird file..." - try: - print Popen(["/this/path/does/not/exist"]).communicate() - except OSError, e: - if e.errno == errno.ENOENT: - print "The file didn't exist. I thought so..." - print "Child traceback:" - print e.child_traceback - else: - print "Error", e.errno - else: - print >>sys.stderr, "Gosh. No error." - - -def _demo_windows(): - # - # Example 1: Connecting several subprocesses - # - print "Looking for 'PROMPT' in set output..." - p1 = Popen("set", stdout=PIPE, shell=True) - p2 = Popen('find "PROMPT"', stdin=p1.stdout, stdout=PIPE) - print repr(p2.communicate()[0]) - - # - # Example 2: Simple execution of program - # - print "Executing calc..." - p = Popen("calc") - p.wait() - - -if __name__ == "__main__": - if mswindows: - _demo_windows() - else: - _demo_posix() diff --git a/config.mak.in b/config.mak.in index 1cafa19..9a57840 100644 --- a/config.mak.in +++ b/config.mak.in @@ -13,7 +13,6 @@ bindir = @bindir@ #gitexecdir = @libexecdir@/git-core/ datarootdir = @datarootdir@ template_dir = @datadir@/git-core/templates/ -GIT_PYTHON_DIR = @datadir@/git-core/python mandir=@mandir@ @@ -23,7 +22,6 @@ VPATH = @srcdir@ export exec_prefix mandir export srcdir VPATH -NO_PYTHON=@NO_PYTHON@ NEEDS_SSL_WITH_CRYPTO=@NEEDS_SSL_WITH_CRYPTO@ NO_OPENSSL=@NO_OPENSSL@ NO_CURL=@NO_CURL@ diff --git a/configure.ac b/configure.ac index cff5722..34e3478 100644 --- a/configure.ac +++ b/configure.ac @@ -75,20 +75,6 @@ GIT_ARG_SET_PATH(shell) # Define PERL_PATH to provide path to Perl. GIT_ARG_SET_PATH(perl) # -# Define PYTHON_PATH to provide path to Python. -AC_ARG_WITH(python,[AS_HELP_STRING([--with-python=PATH], [provide PATH to python]) -AS_HELP_STRING([--without-python], [don't use python scripts])], - [if test "$withval" = "no"; then \ - NO_PYTHON=YesPlease; \ - elif test "$withval" = "yes"; then \ - NO_PYTHON=; \ - else \ - NO_PYTHON=; \ - PYTHON_PATH=$withval; \ - fi; \ - ]) -AC_SUBST(NO_PYTHON) -AC_SUBST(PYTHON_PATH) ## Checks for programs. @@ -98,18 +84,6 @@ AC_PROG_CC([cc gcc]) #AC_PROG_INSTALL # needs install-sh or install.sh in sources AC_CHECK_TOOL(AR, ar, :) AC_CHECK_PROGS(TAR, [gtar tar]) -# -# Define PYTHON_PATH to provide path to Python. -if test -z "$NO_PYTHON"; then - if test -z "$PYTHON_PATH"; then - AC_PATH_PROGS(PYTHON_PATH, [python python2.4 python2.3 python2]) - fi - if test -n "$PYTHON_PATH"; then - GIT_CONF_APPEND_LINE([PYTHON_PATH=@PYTHON_PATH@]) - NO_PYTHON="" - fi -fi - ## Checks for libraries. AC_MSG_NOTICE([CHECKS for libraries]) @@ -262,22 +236,9 @@ AC_SUBST(NO_SETENV) # Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link. # Enable it on Windows. By default, symrefs are still used. # -# Define WITH_OWN_SUBPROCESS_PY if you want to use with python 2.3. -AC_CACHE_CHECK([for subprocess.py], - [ac_cv_python_has_subprocess_py], -[if $PYTHON_PATH -c 'import subprocess' 2>/dev/null; then - ac_cv_python_has_subprocess_py=yes -else - ac_cv_python_has_subprocess_py=no -fi]) -if test $ac_cv_python_has_subprocess_py != yes; then - GIT_CONF_APPEND_LINE([WITH_OWN_SUBPROCESS_PY=YesPlease]) -fi -# # Define NO_ACCURATE_DIFF if your diff program at least sometimes misses # a missing newline at the end of the file. - ## Site configuration (override autodetection) ## --with-PACKAGE[=ARG] and --without-PACKAGE AC_MSG_NOTICE([CHECKS for site configuration]) diff --git a/git-merge-recursive-old.py b/git-merge-recursive-old.py deleted file mode 100755 index 4039435..0000000 --- a/git-merge-recursive-old.py +++ /dev/null @@ -1,944 +0,0 @@ -#!/usr/bin/python -# -# Copyright (C) 2005 Fredrik Kuivinen -# - -import sys -sys.path.append('''@@GIT_PYTHON_PATH@@''') - -import math, random, os, re, signal, tempfile, stat, errno, traceback -from heapq import heappush, heappop -from sets import Set - -from gitMergeCommon import * - -outputIndent = 0 -def output(*args): - sys.stdout.write(' '*outputIndent) - printList(args) - -originalIndexFile = os.environ.get('GIT_INDEX_FILE', - os.environ.get('GIT_DIR', '.git') + '/index') -temporaryIndexFile = os.environ.get('GIT_DIR', '.git') + \ - '/merge-recursive-tmp-index' -def setupIndex(temporary): - try: - os.unlink(temporaryIndexFile) - except OSError: - pass - if temporary: - newIndex = temporaryIndexFile - else: - newIndex = originalIndexFile - os.environ['GIT_INDEX_FILE'] = newIndex - -# This is a global variable which is used in a number of places but -# only written to in the 'merge' function. - -# cacheOnly == True => Don't leave any non-stage 0 entries in the cache and -# don't update the working directory. -# False => Leave unmerged entries in the cache and update -# the working directory. - -cacheOnly = False - -# The entry point to the merge code -# --------------------------------- - -def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0, ancestor=None): - '''Merge the commits h1 and h2, return the resulting virtual - commit object and a flag indicating the cleanness of the merge.''' - assert(isinstance(h1, Commit) and isinstance(h2, Commit)) - - global outputIndent - - output('Merging:') - output(h1) - output(h2) - sys.stdout.flush() - - if ancestor: - ca = [ancestor] - else: - assert(isinstance(graph, Graph)) - ca = getCommonAncestors(graph, h1, h2) - output('found', len(ca), 'common ancestor(s):') - for x in ca: - output(x) - sys.stdout.flush() - - mergedCA = ca[0] - for h in ca[1:]: - outputIndent = callDepth+1 - [mergedCA, dummy] = merge(mergedCA, h, - 'Temporary merge branch 1', - 'Temporary merge branch 2', - graph, callDepth+1) - outputIndent = callDepth - assert(isinstance(mergedCA, Commit)) - - global cacheOnly - if callDepth == 0: - setupIndex(False) - cacheOnly = False - else: - setupIndex(True) - runProgram(['git-read-tree', h1.tree()]) - cacheOnly = True - - [shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), mergedCA.tree(), - branch1Name, branch2Name) - - if graph and (clean or cacheOnly): - res = Commit(None, [h1, h2], tree=shaRes) - graph.addNode(res) - else: - res = None - - return [res, clean] - -getFilesRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)$', re.S) -def getFilesAndDirs(tree): - files = Set() - dirs = Set() - out = runProgram(['git-ls-tree', '-r', '-z', '-t', tree]) - for l in out.split('\0'): - m = getFilesRE.match(l) - if m: - if m.group(2) == 'tree': - dirs.add(m.group(4)) - elif m.group(2) == 'blob': - files.add(m.group(4)) - - return [files, dirs] - -# Those two global variables are used in a number of places but only -# written to in 'mergeTrees' and 'uniquePath'. They keep track of -# every file and directory in the two branches that are about to be -# merged. -currentFileSet = None -currentDirectorySet = None - -def mergeTrees(head, merge, common, branch1Name, branch2Name): - '''Merge the trees 'head' and 'merge' with the common ancestor - 'common'. The name of the head branch is 'branch1Name' and the name of - the merge branch is 'branch2Name'. Return a tuple (tree, cleanMerge) - where tree is the resulting tree and cleanMerge is True iff the - merge was clean.''' - - assert(isSha(head) and isSha(merge) and isSha(common)) - - if common == merge: - output('Already uptodate!') - return [head, True] - - if cacheOnly: - updateArg = '-i' - else: - updateArg = '-u' - - [out, code] = runProgram(['git-read-tree', updateArg, '-m', - common, head, merge], returnCode = True) - if code != 0: - die('git-read-tree:', out) - - [tree, code] = runProgram('git-write-tree', returnCode=True) - tree = tree.rstrip() - if code != 0: - global currentFileSet, currentDirectorySet - [currentFileSet, currentDirectorySet] = getFilesAndDirs(head) - [filesM, dirsM] = getFilesAndDirs(merge) - currentFileSet.union_update(filesM) - currentDirectorySet.union_update(dirsM) - - entries = unmergedCacheEntries() - renamesHead = getRenames(head, common, head, merge, entries) - renamesMerge = getRenames(merge, common, head, merge, entries) - - cleanMerge = processRenames(renamesHead, renamesMerge, - branch1Name, branch2Name) - for entry in entries: - if entry.processed: - continue - if not processEntry(entry, branch1Name, branch2Name): - cleanMerge = False - - if cleanMerge or cacheOnly: - tree = runProgram('git-write-tree').rstrip() - else: - tree = None - else: - cleanMerge = True - - return [tree, cleanMerge] - -# Low level file merging, update and removal -# ------------------------------------------ - -def mergeFile(oPath, oSha, oMode, aPath, aSha, aMode, bPath, bSha, bMode, - branch1Name, branch2Name): - - merge = False - clean = True - - if stat.S_IFMT(aMode) != stat.S_IFMT(bMode): - clean = False - if stat.S_ISREG(aMode): - mode = aMode - sha = aSha - else: - mode = bMode - sha = bSha - else: - if aSha != oSha and bSha != oSha: - merge = True - - if aMode == oMode: - mode = bMode - else: - mode = aMode - - if aSha == oSha: - sha = bSha - elif bSha == oSha: - sha = aSha - elif stat.S_ISREG(aMode): - assert(stat.S_ISREG(bMode)) - - orig = runProgram(['git-unpack-file', oSha]).rstrip() - src1 = runProgram(['git-unpack-file', aSha]).rstrip() - src2 = runProgram(['git-unpack-file', bSha]).rstrip() - try: - [out, code] = runProgram(['merge', - '-L', branch1Name + '/' + aPath, - '-L', 'orig/' + oPath, - '-L', branch2Name + '/' + bPath, - src1, orig, src2], returnCode=True) - except ProgramError, e: - print >>sys.stderr, e - die("Failed to execute 'merge'. merge(1) is used as the " - "file-level merge tool. Is 'merge' in your path?") - - sha = runProgram(['git-hash-object', '-t', 'blob', '-w', - src1]).rstrip() - - os.unlink(orig) - os.unlink(src1) - os.unlink(src2) - - clean = (code == 0) - else: - assert(stat.S_ISLNK(aMode) and stat.S_ISLNK(bMode)) - sha = aSha - - if aSha != bSha: - clean = False - - return [sha, mode, clean, merge] - -def updateFile(clean, sha, mode, path): - updateCache = cacheOnly or clean - updateWd = not cacheOnly - - return updateFileExt(sha, mode, path, updateCache, updateWd) - -def updateFileExt(sha, mode, path, updateCache, updateWd): - if cacheOnly: - updateWd = False - - if updateWd: - pathComponents = path.split('/') - for x in xrange(1, len(pathComponents)): - p = '/'.join(pathComponents[0:x]) - - try: - createDir = not stat.S_ISDIR(os.lstat(p).st_mode) - except OSError: - createDir = True - - if createDir: - try: - os.mkdir(p) - except OSError, e: - die("Couldn't create directory", p, e.strerror) - - prog = ['git-cat-file', 'blob', sha] - if stat.S_ISREG(mode): - try: - os.unlink(path) - except OSError: - pass - if mode & 0100: - mode = 0777 - else: - mode = 0666 - fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode) - proc = subprocess.Popen(prog, stdout=fd) - proc.wait() - os.close(fd) - elif stat.S_ISLNK(mode): - linkTarget = runProgram(prog) - os.symlink(linkTarget, path) - else: - assert(False) - - if updateWd and updateCache: - runProgram(['git-update-index', '--add', '--', path]) - elif updateCache: - runProgram(['git-update-index', '--add', '--cacheinfo', - '0%o' % mode, sha, path]) - -def setIndexStages(path, - oSHA1, oMode, - aSHA1, aMode, - bSHA1, bMode, - clear=True): - istring = [] - if clear: - istring.append("0 " + ("0" * 40) + "\t" + path + "\0") - if oMode: - istring.append("%o %s %d\t%s\0" % (oMode, oSHA1, 1, path)) - if aMode: - istring.append("%o %s %d\t%s\0" % (aMode, aSHA1, 2, path)) - if bMode: - istring.append("%o %s %d\t%s\0" % (bMode, bSHA1, 3, path)) - - runProgram(['git-update-index', '-z', '--index-info'], - input="".join(istring)) - -def removeFile(clean, path): - updateCache = cacheOnly or clean - updateWd = not cacheOnly - - if updateCache: - runProgram(['git-update-index', '--force-remove', '--', path]) - - if updateWd: - try: - os.unlink(path) - except OSError, e: - if e.errno != errno.ENOENT and e.errno != errno.EISDIR: - raise - try: - os.removedirs(os.path.dirname(path)) - except OSError: - pass - -def uniquePath(path, branch): - def fileExists(path): - try: - os.lstat(path) - return True - except OSError, e: - if e.errno == errno.ENOENT: - return False - else: - raise - - branch = branch.replace('/', '_') - newPath = path + '~' + branch - suffix = 0 - while newPath in currentFileSet or \ - newPath in currentDirectorySet or \ - fileExists(newPath): - suffix += 1 - newPath = path + '~' + branch + '_' + str(suffix) - currentFileSet.add(newPath) - return newPath - -# Cache entry management -# ---------------------- - -class CacheEntry: - def __init__(self, path): - class Stage: - def __init__(self): - self.sha1 = None - self.mode = None - - # Used for debugging only - def __str__(self): - if self.mode != None: - m = '0%o' % self.mode - else: - m = 'None' - - if self.sha1: - sha1 = self.sha1 - else: - sha1 = 'None' - return 'sha1: ' + sha1 + ' mode: ' + m - - self.stages = [Stage(), Stage(), Stage(), Stage()] - self.path = path - self.processed = False - - def __str__(self): - return 'path: ' + self.path + ' stages: ' + repr([str(x) for x in self.stages]) - -class CacheEntryContainer: - def __init__(self): - self.entries = {} - - def add(self, entry): - self.entries[entry.path] = entry - - def get(self, path): - return self.entries.get(path) - - def __iter__(self): - return self.entries.itervalues() - -unmergedRE = re.compile(r'^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S) -def unmergedCacheEntries(): - '''Create a dictionary mapping file names to CacheEntry - objects. The dictionary contains one entry for every path with a - non-zero stage entry.''' - - lines = runProgram(['git-ls-files', '-z', '--unmerged']).split('\0') - lines.pop() - - res = CacheEntryContainer() - for l in lines: - m = unmergedRE.match(l) - if m: - mode = int(m.group(1), 8) - sha1 = m.group(2) - stage = int(m.group(3)) - path = m.group(4) - - e = res.get(path) - if not e: - e = CacheEntry(path) - res.add(e) - - e.stages[stage].mode = mode - e.stages[stage].sha1 = sha1 - else: - die('Error: Merge program failed: Unexpected output from', - 'git-ls-files:', l) - return res - -lsTreeRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)\n$', re.S) -def getCacheEntry(path, origTree, aTree, bTree): - '''Returns a CacheEntry object which doesn't have to correspond to - a real cache entry in Git's index.''' - - def parse(out): - if out == '': - return [None, None] - else: - m = lsTreeRE.match(out) - if not m: - die('Unexpected output from git-ls-tree:', out) - elif m.group(2) == 'blob': - return [m.group(3), int(m.group(1), 8)] - else: - return [None, None] - - res = CacheEntry(path) - - [oSha, oMode] = parse(runProgram(['git-ls-tree', origTree, '--', path])) - [aSha, aMode] = parse(runProgram(['git-ls-tree', aTree, '--', path])) - [bSha, bMode] = parse(runProgram(['git-ls-tree', bTree, '--', path])) - - res.stages[1].sha1 = oSha - res.stages[1].mode = oMode - res.stages[2].sha1 = aSha - res.stages[2].mode = aMode - res.stages[3].sha1 = bSha - res.stages[3].mode = bMode - - return res - -# Rename detection and handling -# ----------------------------- - -class RenameEntry: - def __init__(self, - src, srcSha, srcMode, srcCacheEntry, - dst, dstSha, dstMode, dstCacheEntry, - score): - self.srcName = src - self.srcSha = srcSha - self.srcMode = srcMode - self.srcCacheEntry = srcCacheEntry - self.dstName = dst - self.dstSha = dstSha - self.dstMode = dstMode - self.dstCacheEntry = dstCacheEntry - self.score = score - - self.processed = False - -class RenameEntryContainer: - def __init__(self): - self.entriesSrc = {} - self.entriesDst = {} - - def add(self, entry): - self.entriesSrc[entry.srcName] = entry - self.entriesDst[entry.dstName] = entry - - def getSrc(self, path): - return self.entriesSrc.get(path) - - def getDst(self, path): - return self.entriesDst.get(path) - - def __iter__(self): - return self.entriesSrc.itervalues() - -parseDiffRenamesRE = re.compile('^:([0-7]+) ([0-7]+) ([0-9a-f]{40}) ([0-9a-f]{40}) R([0-9]*)$') -def getRenames(tree, oTree, aTree, bTree, cacheEntries): - '''Get information of all renames which occured between 'oTree' and - 'tree'. We need the three trees in the merge ('oTree', 'aTree' and - 'bTree') to be able to associate the correct cache entries with - the rename information. 'tree' is always equal to either aTree or bTree.''' - - assert(tree == aTree or tree == bTree) - inp = runProgram(['git-diff-tree', '-M', '--diff-filter=R', '-r', - '-z', oTree, tree]) - - ret = RenameEntryContainer() - try: - recs = inp.split("\0") - recs.pop() # remove last entry (which is '') - it = recs.__iter__() - while True: - rec = it.next() - m = parseDiffRenamesRE.match(rec) - - if not m: - die('Unexpected output from git-diff-tree:', rec) - - srcMode = int(m.group(1), 8) - dstMode = int(m.group(2), 8) - srcSha = m.group(3) - dstSha = m.group(4) - score = m.group(5) - src = it.next() - dst = it.next() - - srcCacheEntry = cacheEntries.get(src) - if not srcCacheEntry: - srcCacheEntry = getCacheEntry(src, oTree, aTree, bTree) - cacheEntries.add(srcCacheEntry) - - dstCacheEntry = cacheEntries.get(dst) - if not dstCacheEntry: - dstCacheEntry = getCacheEntry(dst, oTree, aTree, bTree) - cacheEntries.add(dstCacheEntry) - - ret.add(RenameEntry(src, srcSha, srcMode, srcCacheEntry, - dst, dstSha, dstMode, dstCacheEntry, - score)) - except StopIteration: - pass - return ret - -def fmtRename(src, dst): - srcPath = src.split('/') - dstPath = dst.split('/') - path = [] - endIndex = min(len(srcPath), len(dstPath)) - 1 - for x in range(0, endIndex): - if srcPath[x] == dstPath[x]: - path.append(srcPath[x]) - else: - endIndex = x - break - - if len(path) > 0: - return '/'.join(path) + \ - '/{' + '/'.join(srcPath[endIndex:]) + ' => ' + \ - '/'.join(dstPath[endIndex:]) + '}' - else: - return src + ' => ' + dst - -def processRenames(renamesA, renamesB, branchNameA, branchNameB): - srcNames = Set() - for x in renamesA: - srcNames.add(x.srcName) - for x in renamesB: - srcNames.add(x.srcName) - - cleanMerge = True - for path in srcNames: - if renamesA.getSrc(path): - renames1 = renamesA - renames2 = renamesB - branchName1 = branchNameA - branchName2 = branchNameB - else: - renames1 = renamesB - renames2 = renamesA - branchName1 = branchNameB - branchName2 = branchNameA - - ren1 = renames1.getSrc(path) - ren2 = renames2.getSrc(path) - - ren1.dstCacheEntry.processed = True - ren1.srcCacheEntry.processed = True - - if ren1.processed: - continue - - ren1.processed = True - - if ren2: - # Renamed in 1 and renamed in 2 - assert(ren1.srcName == ren2.srcName) - ren2.dstCacheEntry.processed = True - ren2.processed = True - - if ren1.dstName != ren2.dstName: - output('CONFLICT (rename/rename): Rename', - fmtRename(path, ren1.dstName), 'in branch', branchName1, - 'rename', fmtRename(path, ren2.dstName), 'in', - branchName2) - cleanMerge = False - - if ren1.dstName in currentDirectorySet: - dstName1 = uniquePath(ren1.dstName, branchName1) - output(ren1.dstName, 'is a directory in', branchName2, - 'adding as', dstName1, 'instead.') - removeFile(False, ren1.dstName) - else: - dstName1 = ren1.dstName - - if ren2.dstName in currentDirectorySet: - dstName2 = uniquePath(ren2.dstName, branchName2) - output(ren2.dstName, 'is a directory in', branchName1, - 'adding as', dstName2, 'instead.') - removeFile(False, ren2.dstName) - else: - dstName2 = ren2.dstName - setIndexStages(dstName1, - None, None, - ren1.dstSha, ren1.dstMode, - None, None) - setIndexStages(dstName2, - None, None, - None, None, - ren2.dstSha, ren2.dstMode) - - else: - removeFile(True, ren1.srcName) - - [resSha, resMode, clean, merge] = \ - mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode, - ren1.dstName, ren1.dstSha, ren1.dstMode, - ren2.dstName, ren2.dstSha, ren2.dstMode, - branchName1, branchName2) - - if merge or not clean: - output('Renaming', fmtRename(path, ren1.dstName)) - - if merge: - output('Auto-merging', ren1.dstName) - - if not clean: - output('CONFLICT (content): merge conflict in', - ren1.dstName) - cleanMerge = False - - if not cacheOnly: - setIndexStages(ren1.dstName, - ren1.srcSha, ren1.srcMode, - ren1.dstSha, ren1.dstMode, - ren2.dstSha, ren2.dstMode) - - updateFile(clean, resSha, resMode, ren1.dstName) - else: - removeFile(True, ren1.srcName) - - # Renamed in 1, maybe changed in 2 - if renamesA == renames1: - stage = 3 - else: - stage = 2 - - srcShaOtherBranch = ren1.srcCacheEntry.stages[stage].sha1 - srcModeOtherBranch = ren1.srcCacheEntry.stages[stage].mode - - dstShaOtherBranch = ren1.dstCacheEntry.stages[stage].sha1 - dstModeOtherBranch = ren1.dstCacheEntry.stages[stage].mode - - tryMerge = False - - if ren1.dstName in currentDirectorySet: - newPath = uniquePath(ren1.dstName, branchName1) - output('CONFLICT (rename/directory): Rename', - fmtRename(ren1.srcName, ren1.dstName), 'in', branchName1, - 'directory', ren1.dstName, 'added in', branchName2) - output('Renaming', ren1.srcName, 'to', newPath, 'instead') - cleanMerge = False - removeFile(False, ren1.dstName) - updateFile(False, ren1.dstSha, ren1.dstMode, newPath) - elif srcShaOtherBranch == None: - output('CONFLICT (rename/delete): Rename', - fmtRename(ren1.srcName, ren1.dstName), 'in', - branchName1, 'and deleted in', branchName2) - cleanMerge = False - updateFile(False, ren1.dstSha, ren1.dstMode, ren1.dstName) - elif dstShaOtherBranch: - newPath = uniquePath(ren1.dstName, branchName2) - output('CONFLICT (rename/add): Rename', - fmtRename(ren1.srcName, ren1.dstName), 'in', - branchName1 + '.', ren1.dstName, 'added in', branchName2) - output('Adding as', newPath, 'instead') - updateFile(False, dstShaOtherBranch, dstModeOtherBranch, newPath) - cleanMerge = False - tryMerge = True - elif renames2.getDst(ren1.dstName): - dst2 = renames2.getDst(ren1.dstName) - newPath1 = uniquePath(ren1.dstName, branchName1) - newPath2 = uniquePath(dst2.dstName, branchName2) - output('CONFLICT (rename/rename): Rename', - fmtRename(ren1.srcName, ren1.dstName), 'in', - branchName1+'. Rename', - fmtRename(dst2.srcName, dst2.dstName), 'in', branchName2) - output('Renaming', ren1.srcName, 'to', newPath1, 'and', - dst2.srcName, 'to', newPath2, 'instead') - removeFile(False, ren1.dstName) - updateFile(False, ren1.dstSha, ren1.dstMode, newPath1) - updateFile(False, dst2.dstSha, dst2.dstMode, newPath2) - dst2.processed = True - cleanMerge = False - else: - tryMerge = True - - if tryMerge: - - oName, oSHA1, oMode = ren1.srcName, ren1.srcSha, ren1.srcMode - aName, bName = ren1.dstName, ren1.srcName - aSHA1, bSHA1 = ren1.dstSha, srcShaOtherBranch - aMode, bMode = ren1.dstMode, srcModeOtherBranch - aBranch, bBranch = branchName1, branchName2 - - if renamesA != renames1: - aName, bName = bName, aName - aSHA1, bSHA1 = bSHA1, aSHA1 - aMode, bMode = bMode, aMode - aBranch, bBranch = bBranch, aBranch - - [resSha, resMode, clean, merge] = \ - mergeFile(oName, oSHA1, oMode, - aName, aSHA1, aMode, - bName, bSHA1, bMode, - aBranch, bBranch); - - if merge or not clean: - output('Renaming', fmtRename(ren1.srcName, ren1.dstName)) - - if merge: - output('Auto-merging', ren1.dstName) - - if not clean: - output('CONFLICT (rename/modify): Merge conflict in', - ren1.dstName) - cleanMerge = False - - if not cacheOnly: - setIndexStages(ren1.dstName, - oSHA1, oMode, - aSHA1, aMode, - bSHA1, bMode) - - updateFile(clean, resSha, resMode, ren1.dstName) - - return cleanMerge - -# Per entry merge function -# ------------------------ - -def processEntry(entry, branch1Name, branch2Name): - '''Merge one cache entry.''' - - debug('processing', entry.path, 'clean cache:', cacheOnly) - - cleanMerge = True - - path = entry.path - oSha = entry.stages[1].sha1 - oMode = entry.stages[1].mode - aSha = entry.stages[2].sha1 - aMode = entry.stages[2].mode - bSha = entry.stages[3].sha1 - bMode = entry.stages[3].mode - - assert(oSha == None or isSha(oSha)) - assert(aSha == None or isSha(aSha)) - assert(bSha == None or isSha(bSha)) - - assert(oMode == None or type(oMode) is int) - assert(aMode == None or type(aMode) is int) - assert(bMode == None or type(bMode) is int) - - if (oSha and (not aSha or not bSha)): - # - # Case A: Deleted in one - # - if (not aSha and not bSha) or \ - (aSha == oSha and not bSha) or \ - (not aSha and bSha == oSha): - # Deleted in both or deleted in one and unchanged in the other - if aSha: - output('Removing', path) - removeFile(True, path) - else: - # Deleted in one and changed in the other - cleanMerge = False - if not aSha: - output('CONFLICT (delete/modify):', path, 'deleted in', - branch1Name, 'and modified in', branch2Name + '.', - 'Version', branch2Name, 'of', path, 'left in tree.') - mode = bMode - sha = bSha - else: - output('CONFLICT (modify/delete):', path, 'deleted in', - branch2Name, 'and modified in', branch1Name + '.', - 'Version', branch1Name, 'of', path, 'left in tree.') - mode = aMode - sha = aSha - - updateFile(False, sha, mode, path) - - elif (not oSha and aSha and not bSha) or \ - (not oSha and not aSha and bSha): - # - # Case B: Added in one. - # - if aSha: - addBranch = branch1Name - otherBranch = branch2Name - mode = aMode - sha = aSha - conf = 'file/directory' - else: - addBranch = branch2Name - otherBranch = branch1Name - mode = bMode - sha = bSha - conf = 'directory/file' - - if path in currentDirectorySet: - cleanMerge = False - newPath = uniquePath(path, addBranch) - output('CONFLICT (' + conf + '):', - 'There is a directory with name', path, 'in', - otherBranch + '. Adding', path, 'as', newPath) - - removeFile(False, path) - updateFile(False, sha, mode, newPath) - else: - output('Adding', path) - updateFile(True, sha, mode, path) - - elif not oSha and aSha and bSha: - # - # Case C: Added in both (check for same permissions). - # - if aSha == bSha: - if aMode != bMode: - cleanMerge = False - output('CONFLICT: File', path, - 'added identically in both branches, but permissions', - 'conflict', '0%o' % aMode, '->', '0%o' % bMode) - output('CONFLICT: adding with permission:', '0%o' % aMode) - - updateFile(False, aSha, aMode, path) - else: - # This case is handled by git-read-tree - assert(False) - else: - cleanMerge = False - newPath1 = uniquePath(path, branch1Name) - newPath2 = uniquePath(path, branch2Name) - output('CONFLICT (add/add): File', path, - 'added non-identically in both branches. Adding as', - newPath1, 'and', newPath2, 'instead.') - removeFile(False, path) - updateFile(False, aSha, aMode, newPath1) - updateFile(False, bSha, bMode, newPath2) - - elif oSha and aSha and bSha: - # - # case D: Modified in both, but differently. - # - output('Auto-merging', path) - [sha, mode, clean, dummy] = \ - mergeFile(path, oSha, oMode, - path, aSha, aMode, - path, bSha, bMode, - branch1Name, branch2Name) - if clean: - updateFile(True, sha, mode, path) - else: - cleanMerge = False - output('CONFLICT (content): Merge conflict in', path) - - if cacheOnly: - updateFile(False, sha, mode, path) - else: - updateFileExt(sha, mode, path, updateCache=False, updateWd=True) - else: - die("ERROR: Fatal merge failure, shouldn't happen.") - - return cleanMerge - -def usage(): - die('Usage:', sys.argv[0], ' ... -- ..') - -# main entry point as merge strategy module -# The first parameters up to -- are merge bases, and the rest are heads. - -if len(sys.argv) < 4: - usage() - -bases = [] -for nextArg in xrange(1, len(sys.argv)): - if sys.argv[nextArg] == '--': - if len(sys.argv) != nextArg + 3: - die('Not handling anything other than two heads merge.') - try: - h1 = firstBranch = sys.argv[nextArg + 1] - h2 = secondBranch = sys.argv[nextArg + 2] - except IndexError: - usage() - break - else: - bases.append(sys.argv[nextArg]) - -print 'Merging', h1, 'with', h2 - -try: - h1 = runProgram(['git-rev-parse', '--verify', h1 + '^0']).rstrip() - h2 = runProgram(['git-rev-parse', '--verify', h2 + '^0']).rstrip() - - if len(bases) == 1: - base = runProgram(['git-rev-parse', '--verify', - bases[0] + '^0']).rstrip() - ancestor = Commit(base, None) - [dummy, clean] = merge(Commit(h1, None), Commit(h2, None), - firstBranch, secondBranch, None, 0, - ancestor) - else: - graph = buildGraph([h1, h2]) - [dummy, clean] = merge(graph.shaMap[h1], graph.shaMap[h2], - firstBranch, secondBranch, graph) - - print '' -except: - if isinstance(sys.exc_info()[1], SystemExit): - raise - else: - traceback.print_exc(None, sys.stderr) - sys.exit(2) - -if clean: - sys.exit(0) -else: - sys.exit(1) diff --git a/git-merge.sh b/git-merge.sh index cb09438..84c3acf 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -9,16 +9,13 @@ USAGE='[-n] [--no-commit] [--squash] [-s ]... < LF=' ' -all_strategies='recur recursive recursive-old octopus resolve stupid ours' +all_strategies='recur recursive octopus resolve stupid ours' default_twohead_strategies='recursive' default_octopus_strategies='octopus' no_trivial_merge_strategies='ours' use_strategies= index_merge=t -if test "@@NO_PYTHON@@"; then - all_strategies='recur recursive resolve octopus stupid ours' -fi dropsave() { rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \ diff --git a/git-rebase.sh b/git-rebase.sh index 546fa44..25530df 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -302,15 +302,6 @@ then exit $? fi -if test "@@NO_PYTHON@@" && test "$strategy" = "recursive-old" -then - die 'The recursive-old merge strategy is written in Python, -which this installation of git was not configured with. Please consider -a different merge strategy (e.g. recursive, resolve, or stupid) -or install Python and git with Python support.' - -fi - # start doing a rebase with git-merge # this is rename-aware if the recursive (default) strategy is used diff --git a/git.spec.in b/git.spec.in index 83268fc..f2374b7 100644 --- a/git.spec.in +++ b/git.spec.in @@ -24,7 +24,7 @@ This is a dummy package which brings in all subpackages. %package core Summary: Core git tools Group: Development/Tools -Requires: zlib >= 1.2, rsync, rcs, curl, less, openssh-clients, python >= 2.3, expat +Requires: zlib >= 1.2, rsync, rcs, curl, less, openssh-clients, expat %description core This is a stupid (but extremely fast) directory content manager. It doesn't do a whole lot, but what it _does_ do is track directory diff --git a/gitMergeCommon.py b/gitMergeCommon.py deleted file mode 100644 index fdbf9e4..0000000 --- a/gitMergeCommon.py +++ /dev/null @@ -1,275 +0,0 @@ -# -# Copyright (C) 2005 Fredrik Kuivinen -# - -import sys, re, os, traceback -from sets import Set - -def die(*args): - printList(args, sys.stderr) - sys.exit(2) - -def printList(list, file=sys.stdout): - for x in list: - file.write(str(x)) - file.write(' ') - file.write('\n') - -import subprocess - -# Debugging machinery -# ------------------- - -DEBUG = 0 -functionsToDebug = Set() - -def addDebug(func): - if type(func) == str: - functionsToDebug.add(func) - else: - functionsToDebug.add(func.func_name) - -def debug(*args): - if DEBUG: - funcName = traceback.extract_stack()[-2][2] - if funcName in functionsToDebug: - printList(args) - -# Program execution -# ----------------- - -class ProgramError(Exception): - def __init__(self, progStr, error): - self.progStr = progStr - self.error = error - - def __str__(self): - return self.progStr + ': ' + self.error - -addDebug('runProgram') -def runProgram(prog, input=None, returnCode=False, env=None, pipeOutput=True): - debug('runProgram prog:', str(prog), 'input:', str(input)) - if type(prog) is str: - progStr = prog - else: - progStr = ' '.join(prog) - - try: - if pipeOutput: - stderr = subprocess.STDOUT - stdout = subprocess.PIPE - else: - stderr = None - stdout = None - pop = subprocess.Popen(prog, - shell = type(prog) is str, - stderr=stderr, - stdout=stdout, - stdin=subprocess.PIPE, - env=env) - except OSError, e: - debug('strerror:', e.strerror) - raise ProgramError(progStr, e.strerror) - - if input != None: - pop.stdin.write(input) - pop.stdin.close() - - if pipeOutput: - out = pop.stdout.read() - else: - out = '' - - code = pop.wait() - if returnCode: - ret = [out, code] - else: - ret = out - if code != 0 and not returnCode: - debug('error output:', out) - debug('prog:', prog) - raise ProgramError(progStr, out) -# debug('output:', out.replace('\0', '\n')) - return ret - -# Code for computing common ancestors -# ----------------------------------- - -currentId = 0 -def getUniqueId(): - global currentId - currentId += 1 - return currentId - -# The 'virtual' commit objects have SHAs which are integers -shaRE = re.compile('^[0-9a-f]{40}$') -def isSha(obj): - return (type(obj) is str and bool(shaRE.match(obj))) or \ - (type(obj) is int and obj >= 1) - -class Commit(object): - __slots__ = ['parents', 'firstLineMsg', 'children', '_tree', 'sha', - 'virtual'] - - def __init__(self, sha, parents, tree=None): - self.parents = parents - self.firstLineMsg = None - self.children = [] - - if tree: - tree = tree.rstrip() - assert(isSha(tree)) - self._tree = tree - - if not sha: - self.sha = getUniqueId() - self.virtual = True - self.firstLineMsg = 'virtual commit' - assert(isSha(tree)) - else: - self.virtual = False - self.sha = sha.rstrip() - assert(isSha(self.sha)) - - def tree(self): - self.getInfo() - assert(self._tree != None) - return self._tree - - def shortInfo(self): - self.getInfo() - return str(self.sha) + ' ' + self.firstLineMsg - - def __str__(self): - return self.shortInfo() - - def getInfo(self): - if self.virtual or self.firstLineMsg != None: - return - else: - info = runProgram(['git-cat-file', 'commit', self.sha]) - info = info.split('\n') - msg = False - for l in info: - if msg: - self.firstLineMsg = l - break - else: - if l.startswith('tree'): - self._tree = l[5:].rstrip() - elif l == '': - msg = True - -class Graph: - def __init__(self): - self.commits = [] - self.shaMap = {} - - def addNode(self, node): - assert(isinstance(node, Commit)) - self.shaMap[node.sha] = node - self.commits.append(node) - for p in node.parents: - p.children.append(node) - return node - - def reachableNodes(self, n1, n2): - res = {} - def traverse(n): - res[n] = True - for p in n.parents: - traverse(p) - - traverse(n1) - traverse(n2) - return res - - def fixParents(self, node): - for x in range(0, len(node.parents)): - node.parents[x] = self.shaMap[node.parents[x]] - -# addDebug('buildGraph') -def buildGraph(heads): - debug('buildGraph heads:', heads) - for h in heads: - assert(isSha(h)) - - g = Graph() - - out = runProgram(['git-rev-list', '--parents'] + heads) - for l in out.split('\n'): - if l == '': - continue - shas = l.split(' ') - - # This is a hack, we temporarily use the 'parents' attribute - # to contain a list of SHA1:s. They are later replaced by proper - # Commit objects. - c = Commit(shas[0], shas[1:]) - - g.commits.append(c) - g.shaMap[c.sha] = c - - for c in g.commits: - g.fixParents(c) - - for c in g.commits: - for p in c.parents: - p.children.append(c) - return g - -# Write the empty tree to the object database and return its SHA1 -def writeEmptyTree(): - tmpIndex = os.environ.get('GIT_DIR', '.git') + '/merge-tmp-index' - def delTmpIndex(): - try: - os.unlink(tmpIndex) - except OSError: - pass - delTmpIndex() - newEnv = os.environ.copy() - newEnv['GIT_INDEX_FILE'] = tmpIndex - res = runProgram(['git-write-tree'], env=newEnv).rstrip() - delTmpIndex() - return res - -def addCommonRoot(graph): - roots = [] - for c in graph.commits: - if len(c.parents) == 0: - roots.append(c) - - superRoot = Commit(sha=None, parents=[], tree=writeEmptyTree()) - graph.addNode(superRoot) - for r in roots: - r.parents = [superRoot] - superRoot.children = roots - return superRoot - -def getCommonAncestors(graph, commit1, commit2): - '''Find the common ancestors for commit1 and commit2''' - assert(isinstance(commit1, Commit) and isinstance(commit2, Commit)) - - def traverse(start, set): - stack = [start] - while len(stack) > 0: - el = stack.pop() - set.add(el) - for p in el.parents: - if p not in set: - stack.append(p) - h1Set = Set() - h2Set = Set() - traverse(commit1, h1Set) - traverse(commit2, h2Set) - shared = h1Set.intersection(h2Set) - - if len(shared) == 0: - shared = [addCommonRoot(graph)] - - res = Set() - - for s in shared: - if len([c for c in s.children if c in shared]) == 0: - res.add(s) - return list(res) diff --git a/t/Makefile b/t/Makefile index 8983509..e1c6c92 100644 --- a/t/Makefile +++ b/t/Makefile @@ -13,10 +13,6 @@ SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh) TSVN = $(wildcard t91[0-9][0-9]-*.sh) -ifdef NO_PYTHON - GIT_TEST_OPTS += --no-python -endif - all: $(T) clean $(T): diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 6aff0b8..81f3bed 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -20,10 +20,10 @@ modification *should* take notice and update the test vectors here. ################################################################ # It appears that people are getting bitten by not installing -# 'merge' (usually part of RCS package in binary distributions) -# or have too old python without subprocess. Check them and error -# out before running any tests. Also catch the bogosity of trying -# to run tests without building while we are at it. +# 'merge' (usually part of RCS package in binary distributions). +# Check this and error out before running any tests. Also catch +# the bogosity of trying to run tests without building while we +# are at it. ../git >/dev/null if test $? != 1 @@ -42,12 +42,6 @@ fi . ./test-lib.sh -test "$no_python" || "$PYTHON" -c 'import subprocess' || { - echo >&2 'Your python seem to lack "subprocess" module. -Please check INSTALL document.' - exit 1 -} - ################################################################ # init-db has been done in an empty repository. # make sure it is empty. diff --git a/t/test-lib.sh b/t/test-lib.sh index 3895f16..ac7be76 100755 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -76,7 +76,8 @@ do -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) verbose=t; shift ;; --no-python) - no_python=t; shift ;; + # noop now... + shift ;; *) break ;; esac @@ -210,18 +211,6 @@ GIT_EXEC_PATH=$(pwd)/.. HOME=$(pwd)/trash export PATH GIT_EXEC_PATH HOME -# Similarly use ../compat/subprocess.py if our python does not -# have subprocess.py on its own. -PYTHON=`sed -e '1{ - s/^#!// - q -}' ../git-merge-recursive-old` || { - error "You haven't built things yet, have you?" -} -"$PYTHON" -c 'import subprocess' 2>/dev/null || { - PYTHONPATH=$(pwd)/../compat - export PYTHONPATH -} GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git export GITPERLLIB test -d ../templates/blt || { -- cgit v0.10.2-6-g49f6 From 17bcdad3b7baa3b12c662663372f1e3cd560dd8e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 20 Nov 2006 01:06:09 -0800 Subject: git-merge: make it usable as the first class UI This teaches the oft-requested syntax git merge $commit to implement merging the named commit to the current branch. This hopefully would make "git merge" usable as the first class UI instead of being a mere backend for "git pull". Most notably, $commit above can be any committish, so you can say for example: git merge js/shortlog~2 to merge early part of a topic branch without merging the rest of it. A custom merge message can be given with the new --message= parameter. The message is prepended in front of the usual "Merge ..." message autogenerated with fmt-merge-message. Signed-off-by: Junio C Hamano diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index bebf30a..e2954aa 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -8,12 +8,14 @@ git-merge - Grand Unified Merge Driver SYNOPSIS -------- -'git-merge' [-n] [--no-commit] [-s ]... ... - +[verse] +'git-merge' [-n] [--no-commit] [--squash] [-s ]... + [--reflog-action=] + -m= ... DESCRIPTION ----------- -This is the top-level user interface to the merge machinery +This is the top-level interface to the merge machinery which drives multiple merge strategy scripts. @@ -27,13 +29,19 @@ include::merge-options.txt[] to give a good default for automated `git-merge` invocations. :: - our branch head commit. + Our branch head commit. This has to be `HEAD`, so new + syntax does not require it :: - other branch head merged into our branch. You need at + Other branch head merged into our branch. You need at least one . Specifying more than one obviously means you are trying an Octopus. +--reflog-action=:: + This is used internally when `git-pull` calls this command + to record that the merge was created by `pull` command + in the `ref-log` entry that results from the merge. + include::merge-strategies.txt[] diff --git a/git-merge.sh b/git-merge.sh index 84c3acf..25deb1e 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -3,7 +3,8 @@ # Copyright (c) 2005 Junio C Hamano # -USAGE='[-n] [--no-commit] [--squash] [-s ]... +' +USAGE='[-n] [--no-commit] [--squash] [-s ] [--reflog-action=] [-m=] +' + . git-sh-setup LF=' @@ -92,7 +93,7 @@ finish () { case "$#" in 0) usage ;; esac -rloga= +rloga= have_message= while case "$#" in 0) break ;; esac do case "$1" in @@ -125,17 +126,63 @@ do --reflog-action=*) rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'` ;; + -m=*|--m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*) + merge_msg=`expr "z$1" : 'z-[^=]*=\(.*\)'` + have_message=t + ;; + -m|--m|--me|--mes|--mess|--messa|--messag|--message) + shift + case "$#" in + 1) usage ;; + esac + merge_msg="$1" + have_message=t + ;; -*) usage ;; *) break ;; esac shift done -merge_msg="$1" -shift -head_arg="$1" -head=$(git-rev-parse --verify "$1"^0) || usage -shift +# This could be traditional "merge HEAD ..." and the +# way we can tell it is to see if the second token is HEAD, but some +# people might have misused the interface and used a committish that +# is the same as HEAD there instead. Traditional format never would +# have "-m" so it is an additional safety measure to check for it. + +if test -z "$have_message" && + second_token=$(git-rev-parse --verify "$2^0" 2>/dev/null) && + head_commit=$(git-rev-parse --verify "HEAD" 2>/dev/null) && + test "$second_token" = "$head_commit" +then + merge_msg="$1" + shift + head_arg="$1" + shift +else + # We are invoked directly as the first-class UI. + head_arg=HEAD + + # All the rest are the commits being merged; prepare + # the standard merge summary message to be appended to + # the given message. If remote is invalid we will die + # later in the common codepath so we discard the error + # in this loop. + merge_name=$(for remote + do + rh=$(git-rev-parse --verify "$remote"^0 2>/dev/null) + if git show-ref -q --verify "refs/heads/$remote" + then + what=branch + else + what=commit + fi + echo "$rh $what '$remote'" + done | git-fmt-merge-msg + ) + merge_msg="${merge_msg:+$merge_msg$LF$LF}$merge_name" +fi +head=$(git-rev-parse --verify "$head_arg"^0) || usage # All the rest are remote heads test "$#" = 0 && usage ;# we need at least one remote head. -- cgit v0.10.2-6-g49f6 From 8092c7f6af044836abf83ed26d542327a4b95c08 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 21 Nov 2006 21:13:28 -0800 Subject: merge: allow merging into a yet-to-be-born branch. Signed-off-by: Junio C Hamano diff --git a/git-merge.sh b/git-merge.sh index 25deb1e..dd4e83d 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -159,6 +159,24 @@ then shift head_arg="$1" shift +elif ! git-rev-parse --verify HEAD 2>/dev/null +then + # If the merged head is a valid one there is no reason to + # forbid "git merge" into a branch yet to be born. We do + # the same for "git pull". + if test 1 -ne $# + then + echo >&2 "Can merge only exactly one commit into empty head" + exit 1 + fi + + rh=$(git rev-parse --verify "$1^0") || + die "$1 - not something we can merge" + + git-update-ref -m "initial pull" HEAD "$rh" "" && + git-read-tree --reset -u HEAD + exit + else # We are invoked directly as the first-class UI. head_arg=HEAD -- cgit v0.10.2-6-g49f6 From f4204ab9f6a192cdb9a68150e031d7183688bfeb Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 21 Nov 2006 23:36:35 -0800 Subject: Store peeled refs in packed-refs (take 2). This fixes the previous implementation which failed to optimize repositories with tons of lightweight tags. The updated packed-refs format begins with "# packed-refs with:" line that lists the kind of extended data the file records. Currently, there is only one such extension defined, "peeled". This stores the "peeled tag" on a line that immediately follows a line for a tag object itself in the format "^". The header line itself and any extended data are ignored by older implementation, so packed-refs file generated with this version can still be used by older git. packed-refs made by older git can of course be used with this version. Signed-off-by: Junio C Hamano diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c index ee5a556..8dc5b9e 100644 --- a/builtin-pack-refs.c +++ b/builtin-pack-refs.c @@ -46,8 +46,8 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, if (o->type == OBJ_TAG) { o = deref_tag(o, path, 0); if (o) - fprintf(cb->refs_file, "%s %s^{}\n", - sha1_to_hex(o->sha1), path); + fprintf(cb->refs_file, "^%s\n", + sha1_to_hex(o->sha1)); } } @@ -111,6 +111,10 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix) if (!cbdata.refs_file) die("unable to create ref-pack file structure (%s)", strerror(errno)); + + /* perhaps other traits later as well */ + fprintf(cbdata.refs_file, "# pack-refs with: peeled \n"); + for_each_ref(handle_one_ref, &cbdata); fflush(cbdata.refs_file); fsync(fd); diff --git a/builtin-show-ref.c b/builtin-show-ref.c index 9ae3d08..0739798 100644 --- a/builtin-show-ref.c +++ b/builtin-show-ref.c @@ -67,8 +67,10 @@ match: return 0; if ((flag & REF_ISPACKED) && !peel_ref(refname, peeled)) { - hex = find_unique_abbrev(peeled, abbrev); - printf("%s %s^{}\n", hex, refname); + if (!is_null_sha1(peeled)) { + hex = find_unique_abbrev(peeled, abbrev); + printf("%s %s^{}\n", hex, refname); + } } else { obj = parse_object(sha1); diff --git a/refs.c b/refs.c index 75cbc0e..96ea8b6 100644 --- a/refs.c +++ b/refs.c @@ -5,14 +5,18 @@ #include +/* ISSYMREF=01 and ISPACKED=02 are public interfaces */ +#define REF_KNOWS_PEELED 04 + struct ref_list { struct ref_list *next; - unsigned char flag; /* ISSYMREF? ISPACKED? ISPEELED? */ + unsigned char flag; /* ISSYMREF? ISPACKED? */ unsigned char sha1[20]; + unsigned char peeled[20]; char name[FLEX_ARRAY]; }; -static const char *parse_ref_line(char *line, unsigned char *sha1, int *flag) +static const char *parse_ref_line(char *line, unsigned char *sha1) { /* * 42: the answer to everything. @@ -23,7 +27,6 @@ static const char *parse_ref_line(char *line, unsigned char *sha1, int *flag) * +1 (newline at the end of the line) */ int len = strlen(line) - 42; - int peeled = 0; if (len <= 0) return NULL; @@ -32,29 +35,18 @@ static const char *parse_ref_line(char *line, unsigned char *sha1, int *flag) if (!isspace(line[40])) return NULL; line += 41; - - if (isspace(*line)) { - /* "SHA-1 SP SP refs/tags/tagname^{} LF"? */ - line++; - len--; - peeled = 1; - } + if (isspace(*line)) + return NULL; if (line[len] != '\n') return NULL; line[len] = 0; - if (peeled && (len < 3 || strcmp(line + len - 3, "^{}"))) - return NULL; - - if (!peeled) - *flag &= ~REF_ISPEELED; - else - *flag |= REF_ISPEELED; return line; } static struct ref_list *add_ref(const char *name, const unsigned char *sha1, - int flag, struct ref_list *list) + int flag, struct ref_list *list, + struct ref_list **new_entry) { int len; struct ref_list **p = &list, *entry; @@ -66,8 +58,11 @@ static struct ref_list *add_ref(const char *name, const unsigned char *sha1, break; /* Same as existing entry? */ - if (!cmp) + if (!cmp) { + if (new_entry) + *new_entry = entry; return list; + } p = &entry->next; } @@ -75,10 +70,13 @@ static struct ref_list *add_ref(const char *name, const unsigned char *sha1, len = strlen(name) + 1; entry = xmalloc(sizeof(struct ref_list) + len); hashcpy(entry->sha1, sha1); + hashclr(entry->peeled); memcpy(entry->name, name, len); entry->flag = flag; entry->next = *p; *p = entry; + if (new_entry) + *new_entry = entry; return list; } @@ -114,27 +112,50 @@ static void invalidate_cached_refs(void) ca->did_loose = ca->did_packed = 0; } +static void read_packed_refs(FILE *f, struct cached_refs *cached_refs) +{ + struct ref_list *list = NULL; + struct ref_list *last = NULL; + char refline[PATH_MAX]; + int flag = REF_ISPACKED; + + while (fgets(refline, sizeof(refline), f)) { + unsigned char sha1[20]; + const char *name; + static const char header[] = "# pack-refs with:"; + + if (!strncmp(refline, header, sizeof(header)-1)) { + const char *traits = refline + sizeof(header) - 1; + if (strstr(traits, " peeled ")) + flag |= REF_KNOWS_PEELED; + /* perhaps other traits later as well */ + continue; + } + + name = parse_ref_line(refline, sha1); + if (name) { + list = add_ref(name, sha1, flag, list, &last); + continue; + } + if (last && + refline[0] == '^' && + strlen(refline) == 42 && + refline[41] == '\n' && + !get_sha1_hex(refline + 1, sha1)) + hashcpy(last->peeled, sha1); + } + cached_refs->packed = list; +} + static struct ref_list *get_packed_refs(void) { if (!cached_refs.did_packed) { - struct ref_list *refs = NULL; FILE *f = fopen(git_path("packed-refs"), "r"); + cached_refs.packed = NULL; if (f) { - struct ref_list *list = NULL; - char refline[PATH_MAX]; - while (fgets(refline, sizeof(refline), f)) { - unsigned char sha1[20]; - int flag = REF_ISPACKED; - const char *name = - parse_ref_line(refline, sha1, &flag); - if (!name) - continue; - list = add_ref(name, sha1, flag, list); - } + read_packed_refs(f, &cached_refs); fclose(f); - refs = list; } - cached_refs.packed = refs; cached_refs.did_packed = 1; } return cached_refs.packed; @@ -177,7 +198,7 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) error("%s points nowhere!", ref); continue; } - list = add_ref(ref, sha1, flag, list); + list = add_ref(ref, sha1, flag, list, NULL); } free(ref); closedir(dir); @@ -225,8 +246,7 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int * if (lstat(path, &st) < 0) { struct ref_list *list = get_packed_refs(); while (list) { - if (!(list->flag & REF_ISPEELED) && - !strcmp(ref, list->name)) { + if (!strcmp(ref, list->name)) { hashcpy(sha1, list->sha1); if (flag) *flag |= REF_ISPACKED; @@ -348,8 +368,6 @@ static int do_one_ref(const char *base, each_ref_fn fn, int trim, return 0; if (is_null_sha1(entry->sha1)) return 0; - if (entry->flag & REF_ISPEELED) - return 0; if (!has_sha1_file(entry->sha1)) { error("%s does not point to a valid object!", entry->name); return 0; @@ -368,22 +386,21 @@ int peel_ref(const char *ref, unsigned char *sha1) if ((flag & REF_ISPACKED)) { struct ref_list *list = get_packed_refs(); - int len = strlen(ref); while (list) { - if ((list->flag & REF_ISPEELED) && - !strncmp(list->name, ref, len) && - strlen(list->name) == len + 3 && - !strcmp(list->name + len, "^{}")) { - hashcpy(sha1, list->sha1); - return 0; + if (!strcmp(list->name, ref)) { + if (list->flag & REF_KNOWS_PEELED) { + hashcpy(sha1, list->peeled); + return 0; + } + /* older pack-refs did not leave peeled ones */ + break; } list = list->next; } - /* older pack-refs did not leave peeled ones in */ } - /* otherwise ... */ + /* fallback - callers should not call this for unpacked refs */ o = parse_object(base); if (o->type == OBJ_TAG) { o = deref_tag(o, ref, 0); diff --git a/refs.h b/refs.h index 40048a6..cd1e1d6 100644 --- a/refs.h +++ b/refs.h @@ -10,14 +10,13 @@ struct ref_lock { int force_write; }; +#define REF_ISSYMREF 01 +#define REF_ISPACKED 02 + /* * Calls the specified function for each ref file until it returns nonzero, * and returns the value */ -#define REF_ISSYMREF 01 -#define REF_ISPACKED 02 -#define REF_ISPEELED 04 /* internal use */ - typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data); extern int head_ref(each_ref_fn, void *); extern int for_each_ref(each_ref_fn, void *); -- cgit v0.10.2-6-g49f6 From 3cd204e518ce832402c291be73292baa149d0240 Mon Sep 17 00:00:00 2001 From: Paul Mackerras Date: Thu, 23 Nov 2006 21:06:16 +1100 Subject: gitk: Fix enabling/disabling of menu items on Mac OS X It seems that under Mac OS X, the menus get some extra entries (or possibly fewer entries), leading to references to entries by an absolute number being off. This leads to an error when invoking gitk --all under Mac OS X, because the "Edit view" and "Delete view" entries aren't were gitk expects them, and so enabling them gives an error. This changes the code so it refers to menu entries by their content, which should solve the problem. Signed-off-by: Paul Mackerras diff --git a/gitk b/gitk index ab383b3..3dabc69 100755 --- a/gitk +++ b/gitk @@ -554,7 +554,7 @@ proc makewindow {} { pack .ctop.top.lbar.vlabel -side left -fill y global viewhlmenu selectedhlview set viewhlmenu [tk_optionMenu .ctop.top.lbar.vhl selectedhlview None] - $viewhlmenu entryconf 0 -command delvhighlight + $viewhlmenu entryconf None -command delvhighlight $viewhlmenu conf -font $uifont .ctop.top.lbar.vhl conf -font $uifont pack .ctop.top.lbar.vhl -side left -fill y @@ -1474,7 +1474,7 @@ proc doviewmenu {m first cmd op argv} { proc allviewmenus {n op args} { global viewhlmenu - doviewmenu .bar.view 7 [list showview $n] $op $args + doviewmenu .bar.view 5 [list showview $n] $op $args doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args } @@ -1516,7 +1516,7 @@ proc newviewok {top n} { set viewperm($n) $newviewperm($n) if {$newviewname($n) ne $viewname($n)} { set viewname($n) $newviewname($n) - doviewmenu .bar.view 7 [list showview $n] \ + doviewmenu .bar.view 5 [list showview $n] \ entryconf [list -label $viewname($n)] doviewmenu $viewhlmenu 1 [list addvhighlight $n] \ entryconf [list -label $viewname($n) -value $viewname($n)] @@ -1632,8 +1632,8 @@ proc showview {n} { set curview $n set selectedview $n - .bar.view entryconf 2 -state [expr {$n == 0? "disabled": "normal"}] - .bar.view entryconf 3 -state [expr {$n == 0? "disabled": "normal"}] + .bar.view entryconf Edit* -state [expr {$n == 0? "disabled": "normal"}] + .bar.view entryconf Delete* -state [expr {$n == 0? "disabled": "normal"}] if {![info exists viewdata($n)]} { set pending_select $selid @@ -4899,9 +4899,9 @@ proc rowmenu {x y id} { } else { set state normal } - $rowctxmenu entryconfigure 0 -state $state - $rowctxmenu entryconfigure 1 -state $state - $rowctxmenu entryconfigure 2 -state $state + $rowctxmenu entryconfigure "Diff this*" -state $state + $rowctxmenu entryconfigure "Diff selected*" -state $state + $rowctxmenu entryconfigure "Make patch" -state $state set rowmenuid $id tk_popup $rowctxmenu $x $y } @@ -6305,8 +6305,8 @@ if {$cmdline_files ne {} || $revtreeargs ne {}} { set viewargs(1) $revtreeargs set viewperm(1) 0 addviewmenu 1 - .bar.view entryconf 2 -state normal - .bar.view entryconf 3 -state normal + .bar.view entryconf Edit* -state normal + .bar.view entryconf Delete* -state normal } if {[info exists permviews]} { -- cgit v0.10.2-6-g49f6 From 28b8e61fc63b6776a91e8afd03c7171fbf0779b0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 22 Nov 2006 21:57:14 -0800 Subject: git-fetch: reuse ls-remote result. This will become necessary to update the dumb protocol transports to fetch from a repository with packed and then pruned tags. Signed-off-by: Junio C Hamano diff --git a/git-fetch.sh b/git-fetch.sh index eb32476..170c2cb 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -88,6 +88,10 @@ then : >"$GIT_DIR/FETCH_HEAD" fi +# Global that is reused later +ls_remote_result=$(git ls-remote $upload_pack "$remote") || + die "Cannot find the reflist at $remote" + append_fetch_head () { head_="$1" remote_="$2" @@ -233,10 +237,7 @@ reflist=$(get_remote_refs_for_fetch "$@") if test "$tags" then taglist=`IFS=" " && - ( - git-ls-remote $upload_pack --tags "$remote" || - echo fail ouch - ) | + echo "$ls_remote_result" | while read sha1 name do case "$sha1" in @@ -245,6 +246,8 @@ then esac case "$name" in *^*) continue ;; + refs/tags/*) ;; + *) continue ;; esac if git-check-ref-format "$name" then @@ -431,7 +434,7 @@ case "$no_tags$tags" in # effective only when we are following remote branch # using local tracking branch. taglist=$(IFS=" " && - git-ls-remote $upload_pack --tags "$remote" | + echo "$ls_remote_result" | sed -n -e 's|^\('"$_x40"'\) \(refs/tags/.*\)^{}$|\1 \2|p' \ -e 's|^\('"$_x40"'\) \(refs/tags/.*\)$|\1 \2|p' | while read sha1 name -- cgit v0.10.2-6-g49f6 From 2986c02217f98809d8990e7679edf0f5d99f904d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 22 Nov 2006 22:24:09 -0800 Subject: git-fetch: fix dumb protocol transport to fetch from pack-pruned ref Earlier, commit walkers downloaded loose refs from refs/ hierarchy of the remote side to find where to start walking; this would not work for a repository whose refs are packed and then pruned. With the previous change, we have ls-remote output from the remote in-core; we can use the value from there without requiring loose refs anymore. Signed-off-by: Junio C Hamano diff --git a/git-fetch.sh b/git-fetch.sh index 170c2cb..06b66b9 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -307,22 +307,20 @@ fetch_main () { "`git-repo-config --bool http.noEPSV`" = true ]; then noepsv_opt="--disable-epsv" fi - max_depth=5 - depth=0 - head="ref: $remote_name" - while (expr "z$head" : "zref:" && expr $depth \< $max_depth) >/dev/null - do - remote_name_quoted=$(@@PERL@@ -e ' - my $u = $ARGV[0]; - $u =~ s/^ref:\s*//; - $u =~ s{([^-a-zA-Z0-9/.])}{sprintf"%%%02x",ord($1)}eg; - print "$u"; - ' "$head") - head=$(curl -nsfL $curl_extra_args $noepsv_opt "$remote/$remote_name_quoted") - depth=$( expr \( $depth + 1 \) ) - done + + # Find $remote_name from ls-remote output. + head=$( + IFS=' ' + echo "$ls_remote_result" | + while read sha1 name + do + test "z$name" = "z$remote_name" || continue + echo "$sha1" + break + done + ) expr "z$head" : "z$_x40\$" >/dev/null || - die "Failed to fetch $remote_name from $remote" + die "No such ref $remote_name at $remote" echo >&2 "Fetching $remote_name from $remote using $proto" git-http-fetch -v -a "$head" "$remote/" || exit ;; -- cgit v0.10.2-6-g49f6 From 5677882be721be5e2706a546d90804da8d8d0bd5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 22 Nov 2006 23:15:00 -0800 Subject: git-fetch: allow glob pattern in refspec This adds Andy's refspec glob. You can have a single line: Pull: refs/heads/*:refs/remotes/origin/* in your ".git/remotes/origin" and say "git fetch" to retrieve all refs under heads/ at the remote side to remotes/origin/ in the local repository. Signed-off-by: Junio C Hamano diff --git a/git-parse-remote.sh b/git-parse-remote.sh index c325ef7..e281b7c 100755 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -90,6 +90,39 @@ get_remote_default_refs_for_push () { esac } +# Called from canon_refs_list_for_fetch -d "$remote", which +# is called from get_remote_default_refs_for_fetch to grok +# refspecs that are retrieved from the configuration, but not +# from get_remote_refs_for_fetch when it deals with refspecs +# supplied on the command line. $ls_remote_result has the list +# of refs available at remote. +expand_refs_wildcard () { + for ref + do + # a non glob pattern is given back as-is. + expr "z$ref" : 'zrefs/.*/\*:refs/.*/\*$' >/dev/null || { + echo "$ref" + continue + } + from=`expr "z$ref" : 'z\(refs/.*/\)\*:refs/.*/\*$'` + to=`expr "z$ref" : 'zrefs/.*/\*:\(refs/.*/\)\*$'` + echo "$ls_remote_result" | + ( + IFS=' ' + while read sha1 name + do + mapped=${name#"$from"} + if test "z$name" != "z${name#'^{}'}" || + test "z$name" = "z$mapped" + then + continue + fi + echo "${name}:${to}${mapped}" + done + ) + done +} + # Subroutine to canonicalize remote:local notation. canon_refs_list_for_fetch () { # If called from get_remote_default_refs_for_fetch @@ -107,6 +140,8 @@ canon_refs_list_for_fetch () { merge_branches=$(git-repo-config \ --get-all "branch.${curr_branch}.merge") fi + set x $(expand_refs_wildcard "$@") + shift fi for ref do -- cgit v0.10.2-6-g49f6 From d4f694ba89857a87e259557d0f236c761b4041ef Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 24 Nov 2006 00:26:49 -0800 Subject: Allow git push to delete remote ref. This allows you to say git send-pack $URL :refs/heads/$branch to delete the named remote branch. The refspec $src:$dst means replace the destination ref with the object known as $src on the local side, so this is a natural extension to make an empty $src mean "No object" to delete the target. Signed-off-by: Junio C Hamano diff --git a/connect.c b/connect.c index b9666cc..f7edba8 100644 --- a/connect.c +++ b/connect.c @@ -144,6 +144,7 @@ struct refspec { * +A:B means overwrite remote B with local A. * +A is a shorthand for +A:A. * A is a shorthand for A:A. + * :B means delete remote B. */ static struct refspec *parse_ref_spec(int nr_refspec, char **refspec) { @@ -240,6 +241,13 @@ static struct ref *try_explicit_object_name(const char *name) unsigned char sha1[20]; struct ref *ref; int len; + + if (!*name) { + ref = xcalloc(1, sizeof(*ref) + 20); + strcpy(ref->name, "(delete)"); + hashclr(ref->new_sha1); + return ref; + } if (get_sha1(name, sha1)) return NULL; len = strlen(name) + 1; @@ -262,7 +270,8 @@ static int match_explicit_refs(struct ref *src, struct ref *dst, break; case 0: /* The source could be in the get_sha1() format - * not a reference name. + * not a reference name. :refs/other is a + * way to delete 'other' ref at the remote end. */ matched_src = try_explicit_object_name(rs[i].src); if (matched_src) diff --git a/receive-pack.c b/receive-pack.c index d56898c..1a141dc 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -14,7 +14,7 @@ static int deny_non_fast_forwards = 0; static int unpack_limit = 5000; static int report_status; -static char capabilities[] = "report-status"; +static char capabilities[] = " report-status delete-refs "; static int capabilities_sent; static int receive_pack_config(const char *var, const char *value) @@ -113,12 +113,14 @@ static int update(struct command *cmd) strcpy(new_hex, sha1_to_hex(new_sha1)); strcpy(old_hex, sha1_to_hex(old_sha1)); - if (!has_sha1_file(new_sha1)) { + + if (!is_null_sha1(new_sha1) && !has_sha1_file(new_sha1)) { cmd->error_string = "bad pack"; return error("unpack should have generated %s, " "but I can't find it!", new_hex); } - if (deny_non_fast_forwards && !is_null_sha1(old_sha1)) { + if (deny_non_fast_forwards && !is_null_sha1(new_sha1) && + !is_null_sha1(old_sha1)) { struct commit *old_commit, *new_commit; struct commit_list *bases, *ent; @@ -138,14 +140,22 @@ static int update(struct command *cmd) return error("hook declined to update %s", name); } - lock = lock_any_ref_for_update(name, old_sha1); - if (!lock) { - cmd->error_string = "failed to lock"; - return error("failed to lock %s", name); + if (is_null_sha1(new_sha1)) { + if (delete_ref(name, old_sha1)) { + cmd->error_string = "failed to delete"; + return error("failed to delete %s", name); + } + fprintf(stderr, "%s: %s -> deleted\n", name, old_hex); + } + else { + lock = lock_any_ref_for_update(name, old_sha1); + if (!lock) { + cmd->error_string = "failed to lock"; + return error("failed to lock %s", name); + } + write_ref_sha1(lock, new_sha1, "push"); + fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex); } - write_ref_sha1(lock, new_sha1, "push"); - - fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex); return 0; } @@ -375,6 +385,16 @@ static void report(const char *unpack_status) packet_flush(1); } +static int delete_only(struct command *cmd) +{ + while (cmd) { + if (!is_null_sha1(cmd->new_sha1)) + return 0; + cmd = cmd->next; + } + return 1; +} + int main(int argc, char **argv) { int i; @@ -408,7 +428,10 @@ int main(int argc, char **argv) read_head_info(); if (commands) { - const char *unpack_status = unpack(); + const char *unpack_status = NULL; + + if (!delete_only(commands)) + unpack_status = unpack(); if (!unpack_status) execute_commands(); if (pack_lockfile) diff --git a/send-pack.c b/send-pack.c index 4476666..328dbbc 100644 --- a/send-pack.c +++ b/send-pack.c @@ -271,6 +271,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) int new_refs; int ret = 0; int ask_for_status_report = 0; + int allow_deleting_refs = 0; int expect_status_report = 0; /* No funny business with the matcher */ @@ -280,6 +281,8 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) /* Does the other end support the reporting? */ if (server_supports("report-status")) ask_for_status_report = 1; + if (server_supports("delete-refs")) + allow_deleting_refs = 1; /* match them up */ if (!remote_tail) @@ -299,9 +302,19 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) new_refs = 0; for (ref = remote_refs; ref; ref = ref->next) { char old_hex[60], *new_hex; + int delete_ref; + if (!ref->peer_ref) continue; - if (!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) { + + delete_ref = is_null_sha1(ref->peer_ref->new_sha1); + if (delete_ref && !allow_deleting_refs) { + error("remote does not support deleting refs"); + ret = -2; + continue; + } + if (!delete_ref && + !hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) { if (verbose) fprintf(stderr, "'%s': up-to-date\n", ref->name); continue; @@ -321,9 +334,13 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) * * (3) if both new and old are commit-ish, and new is a * descendant of old, it is OK. + * + * (4) regardless of all of the above, removing :B is + * always allowed. */ if (!force_update && + !delete_ref && !is_zero_sha1(ref->old_sha1) && !ref->force) { if (!has_sha1_file(ref->old_sha1) || @@ -347,12 +364,8 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) } } hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); - if (is_zero_sha1(ref->new_sha1)) { - error("cannot happen anymore"); - ret = -3; - continue; - } - new_refs++; + if (!delete_ref) + new_refs++; strcpy(old_hex, sha1_to_hex(ref->old_sha1)); new_hex = sha1_to_hex(ref->new_sha1); @@ -366,10 +379,16 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) else packet_write(out, "%s %s %s", old_hex, new_hex, ref->name); - fprintf(stderr, "updating '%s'", ref->name); - if (strcmp(ref->name, ref->peer_ref->name)) - fprintf(stderr, " using '%s'", ref->peer_ref->name); - fprintf(stderr, "\n from %s\n to %s\n", old_hex, new_hex); + if (delete_ref) + fprintf(stderr, "deleting '%s'\n", ref->name); + else { + fprintf(stderr, "updating '%s'", ref->name); + if (strcmp(ref->name, ref->peer_ref->name)) + fprintf(stderr, " using '%s'", + ref->peer_ref->name); + fprintf(stderr, "\n from %s\n to %s\n", + old_hex, new_hex); + } } packet_flush(out); diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index 8afb899..28744b3 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -64,6 +64,16 @@ test_expect_success \ cmp victim/.git/refs/heads/master .git/refs/heads/master ' +test_expect_success \ + 'push can be used to delete a ref' ' + cd victim && + git branch extra master && + cd .. && + test -f victim/.git/refs/heads/extra && + git-send-pack ./victim/.git/ :extra master && + ! test -f victim/.git/refs/heads/extra +' + unset GIT_CONFIG GIT_CONFIG_LOCAL HOME=`pwd`/no-such-directory export HOME ;# this way we force the victim/.git/config to be used. -- cgit v0.10.2-6-g49f6 From 983d2ee284936e8bf14823863d3945b4d8740b94 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 24 Nov 2006 19:07:24 -0800 Subject: git-clone: stop dumb protocol from copying refs outside heads/ and tags/. Most notably, the original code first copied refs/remotes/ that remote side had to local, and overwrote them by mapping refs/heads/ from the remote when a dumb protocol transport was used. This makes the clone behaviour by dumb protocol in line with the git native and rsync transports. Signed-off-by: Junio C Hamano diff --git a/git-clone.sh b/git-clone.sh index 9ed4135..d4ee93f 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -48,6 +48,10 @@ Perhaps git-update-server-info needs to be run there?" case "$name" in *^*) continue;; esac + case "$bare,$name" in + yes,* | ,heads/* | ,tags/*) ;; + *) continue ;; + esac if test -n "$use_separate_remote" && branch_name=`expr "z$name" : 'zheads/\(.*\)'` then -- cgit v0.10.2-6-g49f6 From 75e6e2132006770156d9df1881b4114862919c94 Mon Sep 17 00:00:00 2001 From: Lars Hjemli Date: Fri, 24 Nov 2006 14:45:10 +0100 Subject: Add -v and --abbrev options to git-branch The new -v option makes git-branch show the abbreviated sha1 + subjectline for each branch. Additionally, minimum abbreviation length can be specified with --abbrev= Signed-off-by: Lars Hjemli Signed-off-by: Junio C Hamano diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 5376760..4f5b5d5 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -8,7 +8,7 @@ git-branch - List, create, or delete branches. SYNOPSIS -------- [verse] -'git-branch' [-r] [-a] +'git-branch' [-r] [-a] [-v] [--abbrev=] 'git-branch' [-l] [-f] [] 'git-branch' (-d | -D) ... @@ -52,6 +52,13 @@ OPTIONS -a:: List both remote-tracking branches and local branches. +-v:: + Show sha1 and subject message for each head. + +--abbrev=:: + Alter minimum display length for sha1 in output listing, + default value is 7. + :: The name of the branch to create or delete. The new branch name must pass all checks defined by diff --git a/builtin-branch.c b/builtin-branch.c index 22e3285..69b7b55 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -11,7 +11,7 @@ #include "builtin.h" static const char builtin_branch_usage[] = -"git-branch (-d | -D) | [-l] [-f] [] | [-r] | [-a]"; +"git-branch (-d | -D) | [-l] [-f] [] | [-r | -a] [-v] [--abbrev=] "; static const char *head; @@ -87,10 +87,11 @@ static void delete_branches(int argc, const char **argv, int force) struct ref_item { char *name; unsigned int kind; + unsigned char sha1[20]; }; struct ref_list { - int index, alloc; + int index, alloc, maxwidth; struct ref_item *list; int kinds; }; @@ -100,6 +101,7 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, struct ref_list *ref_list = (struct ref_list*)(cb_data); struct ref_item *newitem; int kind = REF_UNKNOWN_TYPE; + int len; /* Detect kind */ if (!strncmp(refname, "refs/heads/", 11)) { @@ -128,6 +130,10 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, newitem = &(ref_list->list[ref_list->index++]); newitem->name = xstrdup(refname); newitem->kind = kind; + hashcpy(newitem->sha1, sha1); + len = strlen(newitem->name); + if (len > ref_list->maxwidth) + ref_list->maxwidth = len; return 0; } @@ -151,7 +157,24 @@ static int ref_cmp(const void *r1, const void *r2) return strcmp(c1->name, c2->name); } -static void print_ref_list(int kinds) +static void print_ref_info(const unsigned char *sha1, int abbrev) +{ + struct commit *commit; + char subject[256]; + + + commit = lookup_commit(sha1); + if (commit && !parse_commit(commit)) + pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0, + subject, sizeof(subject), 0, + NULL, NULL, 0); + else + strcpy(subject, " **** invalid ref ****"); + + printf(" %s %s\n", find_unique_abbrev(sha1, abbrev), subject); +} + +static void print_ref_list(int kinds, int verbose, int abbrev) { int i; char c; @@ -169,7 +192,13 @@ static void print_ref_list(int kinds) !strcmp(ref_list.list[i].name, head)) c = '*'; - printf("%c %s\n", c, ref_list.list[i].name); + if (verbose) { + printf("%c %-*s", c, ref_list.maxwidth, + ref_list.list[i].name); + print_ref_info(ref_list.list[i].sha1, abbrev); + } + else + printf("%c %s\n", c, ref_list.list[i].name); } free_ref_list(&ref_list); @@ -215,6 +244,7 @@ static void create_branch(const char *name, const char *start, int cmd_branch(int argc, const char **argv, const char *prefix) { int delete = 0, force_delete = 0, force_create = 0; + int verbose = 0, abbrev = DEFAULT_ABBREV; int reflog = 0; int kinds = REF_LOCAL_BRANCH; int i; @@ -255,6 +285,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix) reflog = 1; continue; } + if (!strncmp(arg, "--abbrev=", 9)) { + abbrev = atoi(arg+9); + continue; + } + if (!strcmp(arg, "-v")) { + verbose = 1; + continue; + } usage(builtin_branch_usage); } @@ -268,7 +306,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (delete) delete_branches(argc - i, argv + i, force_delete); else if (i == argc) - print_ref_list(kinds); + print_ref_list(kinds, verbose, abbrev); else if (i == argc - 1) create_branch(argv[i], head, force_create, reflog); else if (i == argc - 2) -- cgit v0.10.2-6-g49f6 From 67affd5173da059ca60aab7896985331acacd9b4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 24 Nov 2006 23:10:23 -0800 Subject: git-branch -D: make it work even when on a yet-to-be-born branch This makes "git branch -D other_branch" work even when HEAD points at a yet-to-be-born branch. Earlier, we checked the HEAD ref for the purpose of "subset" check even when the deletion was forced (i.e. not -d but -D). Because of this, you cannot delete a branch even with -D while on a yet-to-be-born branch. With this change, the following sequence that now works: mkdir newdir && cd newdir git init-db git fetch -k $other_repo refs/heads/master:refs/heads/othre # oops, typo git branch other othre git branch -D othre Signed-off-by: Junio C Hamano diff --git a/builtin-branch.c b/builtin-branch.c index 69b7b55..3d5cb0e 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -38,12 +38,16 @@ static int in_merge_bases(const unsigned char *sha1, static void delete_branches(int argc, const char **argv, int force) { - struct commit *rev, *head_rev; + struct commit *rev, *head_rev = head_rev; unsigned char sha1[20]; char *name; int i; - head_rev = lookup_commit_reference(head_sha1); + if (!force) { + head_rev = lookup_commit_reference(head_sha1); + if (!head_rev) + die("Couldn't look up commit object for HEAD"); + } for (i = 0; i < argc; i++) { if (!strcmp(head, argv[i])) die("Cannot delete the branch you are currently on."); @@ -53,8 +57,8 @@ static void delete_branches(int argc, const char **argv, int force) die("Branch '%s' not found.", argv[i]); rev = lookup_commit_reference(sha1); - if (!rev || !head_rev) - die("Couldn't look up commit objects."); + if (!rev) + die("Couldn't look up commit object for '%s'", name); /* This checks whether the merge bases of branch and * HEAD contains branch -- which means that the HEAD -- cgit v0.10.2-6-g49f6 From 747fa12cef73b6ca04fffaddaad7326cf546cdea Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 24 Nov 2006 22:38:17 -0800 Subject: git-svn: correctly access repos when only given partial read permissions Sometimes users are given only read access to a subtree inside a repository, and git-svn could not read log information (and thus fetch commits) when connecting a session to the root of the repository. We now start an SVN::Ra session with the full URL of what we're tracking, and not the repository root as before. This change was made much easier with a cleanup of repo_path_split() usage as well as improving the accounting of authentication batons. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index 47cd3e2..5d67771 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -39,7 +39,7 @@ memoize('revisions_eq'); memoize('cmt_metadata'); memoize('get_commit_time'); -my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib, $AUTH_BATON, $AUTH_CALLBACKS); +my ($SVN, $_use_lib); sub nag_lib { print STDERR < $head) { @@ -426,7 +423,7 @@ sub fetch_lib { # performance sucks with it enabled, so it's much # faster to fetch revision ranges instead of relying # on the limiter. - libsvn_get_log($SVN_LOG, '/'.$SVN_PATH, + libsvn_get_log(libsvn_dup_ra($SVN), [''], $min, $max, 0, 1, 1, sub { my $log_msg; @@ -528,7 +525,6 @@ sub commit_lib { my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$"; my $repo; - ($repo, $SVN_PATH) = repo_path_split($SVN_URL); set_svn_commit_env(); foreach my $c (@revs) { my $log_msg = get_commit_message($c, $commit_msg); @@ -537,13 +533,11 @@ sub commit_lib { # can't track down... (it's probably in the SVN code) defined(my $pid = open my $fh, '-|') or croak $!; if (!$pid) { - $SVN_LOG = libsvn_connect($repo); - $SVN = libsvn_connect($repo); my $ed = SVN::Git::Editor->new( { r => $r_last, - ra => $SVN_LOG, + ra => libsvn_dup_ra($SVN), c => $c, - svn_path => $SVN_PATH + svn_path => $SVN->{svn_path}, }, $SVN->get_commit_editor( $log_msg->{msg}, @@ -661,10 +655,9 @@ sub show_ignore_cmd { sub show_ignore_lib { my $repo; - ($repo, $SVN_PATH) = repo_path_split($SVN_URL); - $SVN ||= libsvn_connect($repo); + $SVN ||= libsvn_connect($SVN_URL); my $r = defined $_revision ? $_revision : $SVN->get_latest_revnum; - libsvn_traverse_ignore(\*STDOUT, $SVN_PATH, $r); + libsvn_traverse_ignore(\*STDOUT, $SVN->{svn_path}, $r); } sub graft_branches { @@ -790,7 +783,7 @@ sub show_log { } elsif (/^:\d{6} \d{6} $sha1_short/o) { push @{$c->{raw}}, $_; } elsif (/^[ACRMDT]\t/) { - # we could add $SVN_PATH here, but that requires + # we could add $SVN->{svn_path} here, but that requires # remote access at the moment (repo_path_split)... s#^([ACRMDT])\t# $1 #; push @{$c->{changed}}, $_; @@ -856,10 +849,7 @@ sub commit_diff { $_message ||= get_commit_message($tb, "$GIT_DIR/.svn-commit.tmp.$$")->{msg}; } - my $repo; - ($repo, $SVN_PATH) = repo_path_split($SVN_URL); - $SVN_LOG ||= libsvn_connect($repo); - $SVN ||= libsvn_connect($repo); + $SVN ||= libsvn_connect($SVN_URL); if ($r eq 'HEAD') { $r = $SVN->get_latest_revnum; } elsif ($r !~ /^\d+$/) { @@ -868,8 +858,9 @@ sub commit_diff { my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : (); my $rev_committed; my $ed = SVN::Git::Editor->new({ r => $r, - ra => $SVN_LOG, c => $tb, - svn_path => $SVN_PATH + ra => libsvn_dup_ra($SVN), + c => $tb, + svn_path => $SVN->{svn_path} }, $SVN->get_commit_editor($_message, sub { @@ -1147,8 +1138,7 @@ sub graft_file_copy_lib { my $tree_paths = $l_map->{$u}; my $pfx = common_prefix([keys %$tree_paths]); my ($repo, $path) = repo_path_split($u.$pfx); - $SVN_LOG ||= libsvn_connect($repo); - $SVN ||= libsvn_connect($repo); + $SVN = libsvn_connect($repo); my ($base, $head) = libsvn_parse_revision(); my $inc = 1000; @@ -1157,7 +1147,8 @@ sub graft_file_copy_lib { $SVN::Error::handler = \&libsvn_skip_unknown_revs; while (1) { my $pool = SVN::Pool->new; - libsvn_get_log($SVN_LOG, "/$path", $min, $max, 0, 1, 1, + libsvn_get_log(libsvn_dup_ra($SVN), [$path], + $min, $max, 0, 1, 1, sub { libsvn_graft_file_copies($grafts, $tree_paths, $path, @_); @@ -1267,13 +1258,9 @@ sub repo_path_split { return ($u, $full_url); } } - if ($_use_lib) { my $tmp = libsvn_connect($full_url); - my $url = $tmp->get_repos_root; - $full_url =~ s#^\Q$url\E/*##; - push @repo_path_split_cache, qr/^(\Q$url\E)/; - return ($url, $full_url); + return ($tmp->{repos_root}, $tmp->{svn_path}); } else { my ($url, $path) = ($full_url =~ m!^([a-z\+]+://[^/]*)(.*)$!i); $path =~ s#^/+##; @@ -2815,34 +2802,41 @@ sub _read_password { sub libsvn_connect { my ($url) = @_; - if (!$AUTH_BATON || !$AUTH_CALLBACKS) { - SVN::_Core::svn_config_ensure($_config_dir, undef); - ($AUTH_BATON, $AUTH_CALLBACKS) = SVN::Core::auth_open_helper([ - SVN::Client::get_simple_provider(), - SVN::Client::get_ssl_server_trust_file_provider(), - SVN::Client::get_simple_prompt_provider( - \&_simple_prompt, 2), - SVN::Client::get_ssl_client_cert_prompt_provider( - \&_ssl_client_cert_prompt, 2), - SVN::Client::get_ssl_client_cert_pw_prompt_provider( - \&_ssl_client_cert_pw_prompt, 2), - SVN::Client::get_username_provider(), - SVN::Client::get_ssl_server_trust_prompt_provider( - \&_ssl_server_trust_prompt), - SVN::Client::get_username_prompt_provider( - \&_username_prompt, 2), - ]); - } - SVN::Ra->new(url => $url, auth => $AUTH_BATON, - auth_provider_callbacks => $AUTH_CALLBACKS); + SVN::_Core::svn_config_ensure($_config_dir, undef); + my ($baton, $callbacks) = SVN::Core::auth_open_helper([ + SVN::Client::get_simple_provider(), + SVN::Client::get_ssl_server_trust_file_provider(), + SVN::Client::get_simple_prompt_provider( + \&_simple_prompt, 2), + SVN::Client::get_ssl_client_cert_prompt_provider( + \&_ssl_client_cert_prompt, 2), + SVN::Client::get_ssl_client_cert_pw_prompt_provider( + \&_ssl_client_cert_pw_prompt, 2), + SVN::Client::get_username_provider(), + SVN::Client::get_ssl_server_trust_prompt_provider( + \&_ssl_server_trust_prompt), + SVN::Client::get_username_prompt_provider( + \&_username_prompt, 2), + ]); + my $ra = SVN::Ra->new(url => $url, auth => $baton, + pool => SVN::Pool->new, + auth_provider_callbacks => $callbacks); + $ra->{svn_path} = $url; + $ra->{repos_root} = $ra->get_repos_root; + $ra->{svn_path} =~ s#^\Q$ra->{repos_root}\E/*##; + push @repo_path_split_cache, qr/^(\Q$ra->{repos_root}\E)/; + return $ra; +} + +sub libsvn_dup_ra { + my ($ra) = @_; + SVN::Ra->new(map { $_ => $ra->{$_} } + qw/url auth auth_provider_callbacks repos_root svn_path/); } sub libsvn_get_file { my ($gui, $f, $rev, $chg) = @_; - my $p = $f; - if (length $SVN_PATH > 0) { - return unless ($p =~ s#^\Q$SVN_PATH\E/##); - } + $f =~ s#^/##; print "\t$chg\t$f\n" unless $_q; my ($hash, $pid, $in, $out); @@ -2879,7 +2873,7 @@ sub libsvn_get_file { waitpid $pid, 0; $hash =~ /^$sha1$/o or die "not a sha1: $hash\n"; } - print $gui $mode,' ',$hash,"\t",$p,"\0" or croak $!; + print $gui $mode,' ',$hash,"\t",$f,"\0" or croak $!; } sub libsvn_log_entry { @@ -2897,7 +2891,6 @@ sub libsvn_log_entry { sub process_rm { my ($gui, $last_commit, $f) = @_; - $f =~ s#^\Q$SVN_PATH\E/?## or return; # remove entire directories. if (safe_qx('git-ls-tree',$last_commit,'--',$f) =~ /^040000 tree/) { defined(my $pid = open my $ls, '-|') or croak $!; @@ -2919,9 +2912,11 @@ sub libsvn_fetch { my ($last_commit, $paths, $rev, $author, $date, $msg) = @_; open my $gui, '| git-update-index -z --index-info' or croak $!; my @amr; + my $p = $SVN->{svn_path}; foreach my $f (keys %$paths) { my $m = $paths->{$f}->action(); - $f =~ s#^/+##; + $f =~ s#^/\Q$p\E/##; + next if $f =~ m#^/#; if ($m =~ /^[DR]$/) { print "\t$m\t$f\n" unless $_q; process_rm($gui, $last_commit, $f); @@ -3011,9 +3006,9 @@ sub libsvn_parse_revision { sub libsvn_traverse { my ($gui, $pfx, $path, $rev, $files) = @_; - my $cwd = "$pfx/$path"; + my $cwd = length $pfx ? "$pfx/$path" : $path; my $pool = SVN::Pool->new; - $cwd =~ s#^/+##g; + $cwd =~ s#^\Q$SVN->{svn_path}\E##; my ($dirent, $r, $props) = $SVN->get_dir($cwd, $rev, $pool); foreach my $d (keys %$dirent) { my $t = $dirent->{$d}->kind; @@ -3037,7 +3032,7 @@ sub libsvn_traverse_ignore { my $pool = SVN::Pool->new; my ($dirent, undef, $props) = $SVN->get_dir($path, $r, $pool); my $p = $path; - $p =~ s#^\Q$SVN_PATH\E/?##; + $p =~ s#^\Q$SVN->{svn_path}\E/##; print $fh length $p ? "\n# $p\n" : "\n# /\n"; if (my $s = $props->{'svn:ignore'}) { $s =~ s/[\r\n]+/\n/g; @@ -3064,7 +3059,7 @@ sub revisions_eq { if ($_use_lib) { # should be OK to use Pool here (r1 - r0) should be small my $pool = SVN::Pool->new; - libsvn_get_log($SVN, "/$path", $r0, $r1, + libsvn_get_log($SVN, [$path], $r0, $r1, 0, 1, 1, sub {$nr++}, $pool); $pool->clear; } else { @@ -3079,7 +3074,7 @@ sub revisions_eq { sub libsvn_find_parent_branch { my ($paths, $rev, $author, $date, $msg) = @_; - my $svn_path = '/'.$SVN_PATH; + my $svn_path = '/'.$SVN->{svn_path}; # look for a parent from another branch: my $i = $paths->{$svn_path} or return; @@ -3090,7 +3085,7 @@ sub libsvn_find_parent_branch { $branch_from =~ s#^/##; my $l_map = {}; read_url_paths_all($l_map, '', "$GIT_DIR/svn"); - my $url = $SVN->{url}; + my $url = $SVN->{repos_root}; defined $l_map->{$url} or return; my $id = $l_map->{$url}->{$branch_from}; if (!defined $id && $_follow_parent) { @@ -3112,7 +3107,7 @@ sub libsvn_find_parent_branch { $GIT_SVN = $ENV{GIT_SVN_ID} = $id; init_vars(); $SVN_URL = "$url/$branch_from"; - $SVN_LOG = $SVN = undef; + $SVN = undef; setup_git_svn(); # we can't assume SVN_URL exists at r+1: $_revision = "0:$r"; @@ -3149,7 +3144,7 @@ sub libsvn_new_tree { } my ($paths, $rev, $author, $date, $msg) = @_; open my $gui, '| git-update-index -z --index-info' or croak $!; - libsvn_traverse($gui, '', $SVN_PATH, $rev); + libsvn_traverse($gui, '', $SVN->{svn_path}, $rev); close $gui or croak $?; return libsvn_log_entry($rev, $author, $date, $msg); } @@ -3234,11 +3229,10 @@ sub libsvn_commit_cb { sub libsvn_ls_fullurl { my $fullurl = shift; - my ($repo, $path) = repo_path_split($fullurl); - $SVN ||= libsvn_connect($repo); + $SVN ||= libsvn_connect($fullurl); my @ret; my $pool = SVN::Pool->new; - my ($dirent, undef, undef) = $SVN->get_dir($path, + my ($dirent, undef, undef) = $SVN->get_dir($SVN->{svn_path}, $SVN->get_latest_revnum, $pool); foreach my $d (keys %$dirent) { if ($dirent->{$d}->kind == $SVN::Node::dir) { @@ -3260,8 +3254,9 @@ sub libsvn_skip_unknown_revs { # Wonderfully consistent library, eh? # 160013 - svn:// and file:// # 175002 - http(s):// + # 175007 - http(s):// (this repo required authorization, too...) # More codes may be discovered later... - if ($errno == 175002 || $errno == 160013) { + if ($errno == 175007 || $errno == 175002 || $errno == 160013) { return; } croak "Error from SVN, ($errno): ", $err->expanded_message,"\n"; @@ -3349,8 +3344,7 @@ sub split_path { } sub repo_path { - (defined $_[1] && length $_[1]) ? "$_[0]->{svn_path}/$_[1]" - : $_[0]->{svn_path} + (defined $_[1] && length $_[1]) ? $_[1] : '' } sub url_path { @@ -3382,10 +3376,9 @@ sub rmdirs { exec qw/git-ls-tree --name-only -r -z/, $self->{c} or croak $!; } local $/ = "\0"; - my @svn_path = split m#/#, $self->{svn_path}; while (<$fh>) { chomp; - my @dn = (@svn_path, (split m#/#, $_)); + my @dn = split m#/#, $_; while (pop @dn) { delete $rm->{join '/', @dn}; } -- cgit v0.10.2-6-g49f6 From d25c26e771fdf771f264dc85be348719886d354f Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 24 Nov 2006 22:38:18 -0800 Subject: git-svn: exit with status 1 for test failures Some versions of the SVN libraries cause die() to exit with 255, and 40cf043389ef4cdf3e56e7c4268d6f302e387fa0 tightened up test_expect_failure to reject return values >128. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index 5d67771..0a47b1f 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -21,6 +21,7 @@ $ENV{TZ} = 'UTC'; $ENV{LC_ALL} = 'C'; $| = 1; # unbuffer STDOUT +sub fatal (@) { print STDERR $@; exit 1 } # If SVN:: library support is added, please make the dependencies # optional and preserve the capability to use the command-line client. # use eval { require SVN::... } to make it lazy load @@ -569,7 +570,7 @@ sub commit_lib { $no = 1; } } - close $fh or croak $?; + close $fh or exit 1; if (! defined $r_new && ! defined $cmt_new) { unless ($no) { die "Failed to parse revision information\n"; @@ -868,13 +869,16 @@ sub commit_diff { print "Committed $_[0]\n"; }, @lock) ); - my $mods = libsvn_checkout_tree($ta, $tb, $ed); - if (@$mods == 0) { - print "No changes\n$ta == $tb\n"; - $ed->abort_edit; - } else { - $ed->close_edit; - } + eval { + my $mods = libsvn_checkout_tree($ta, $tb, $ed); + if (@$mods == 0) { + print "No changes\n$ta == $tb\n"; + $ed->abort_edit; + } else { + $ed->close_edit; + } + }; + fatal "$@\n" if $@; $_message = $_file = undef; return $rev_committed; } -- cgit v0.10.2-6-g49f6 From c95044d4f3c98b52f16e32cfe09f3ff988a80d2a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 25 Nov 2006 00:01:27 -0800 Subject: git-shortlog: fix common repository prefix abbreviation. The code to abbreviate the common repository prefix was totally borked. Signed-off-by: Junio C Hamano diff --git a/builtin-shortlog.c b/builtin-shortlog.c index b760b47..bdd952c 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -130,12 +130,17 @@ static void insert_author_oneline(struct path_list *list, memcpy(buffer, oneline, onelinelen); buffer[onelinelen] = '\0'; - while ((p = strstr(buffer, dot3)) != NULL) { - memcpy(p, "...", 3); - strcpy(p + 2, p + sizeof(dot3) - 1); + if (dot3) { + int dot3len = strlen(dot3); + if (dot3len > 5) { + while ((p = strstr(buffer, dot3)) != NULL) { + int taillen = strlen(p) - dot3len; + memcpy(p, "/.../", 5); + memmove(p + 5, p + dot3len, taillen + 1); + } + } } - onelines = item->util; if (onelines->nr >= onelines->alloc) { onelines->alloc = alloc_nr(onelines->nr); -- cgit v0.10.2-6-g49f6 From 7595e2ee6ef9b35ebc8dc45543723e1d89765ce3 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 25 Nov 2006 00:07:54 -0800 Subject: git-shortlog: make common repository prefix configurable with .mailmap The code had "/pub/scm/linux/kernel/git/" hardcoded which was too specific to the kernel project. With this, a line in the .mailmap file: # repo-abbrev: /pub/scm/linux/kernel/git/ can be used to cause the substring to be abbreviated to /.../ on the title line of the commit message. Signed-off-by: Junio C Hamano diff --git a/builtin-shortlog.c b/builtin-shortlog.c index bdd952c..b5b13de 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -9,6 +9,8 @@ static const char shortlog_usage[] = "git-shortlog [-n] [-s] [... ]"; +static char *common_repo_prefix; + static int compare_by_number(const void *a1, const void *a2) { const struct path_list_item *i1 = a1, *i2 = a2; @@ -35,8 +37,26 @@ static int read_mailmap(const char *filename) char *end_of_name, *left_bracket, *right_bracket; char *name, *email; int i; - if (buffer[0] == '#') + if (buffer[0] == '#') { + static const char abbrev[] = "# repo-abbrev:"; + int abblen = sizeof(abbrev) - 1; + int len = strlen(buffer); + + if (len && buffer[len - 1] == '\n') + buffer[--len] = 0; + if (!strncmp(buffer, abbrev, abblen)) { + char *cp; + + if (common_repo_prefix) + free(common_repo_prefix); + common_repo_prefix = xmalloc(len); + + for (cp = buffer + abblen; isspace(*cp); cp++) + ; /* nothing */ + strcpy(common_repo_prefix, cp); + } continue; + } if ((left_bracket = strchr(buffer, '<')) == NULL) continue; if ((right_bracket = strchr(left_bracket + 1, '>')) == NULL) @@ -87,7 +107,7 @@ static void insert_author_oneline(struct path_list *list, const char *author, int authorlen, const char *oneline, int onelinelen) { - const char *dot3 = "/pub/scm/linux/kernel/git/"; + const char *dot3 = common_repo_prefix; char *buffer, *p; struct path_list_item *item; struct path_list *onelines; diff --git a/contrib/mailmap.linux b/contrib/mailmap.linux index 83927c9..e4907f8 100644 --- a/contrib/mailmap.linux +++ b/contrib/mailmap.linux @@ -3,6 +3,8 @@ # So have an email->real name table to translate the # (hopefully few) missing names # +# repo-abbrev: /pub/scm/linux/kernel/git/ +# Adrian Bunk Andreas Herrmann Andrew Morton -- cgit v0.10.2-6-g49f6 From 61f5cb7f0d9ea6990f331bd7082630691c88abd2 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 24 Oct 2006 21:48:55 -0700 Subject: git-commit: show --summary after successful commit. Sometimes people accidentally commit files in wrong mode bits. Show --summary output for the HEAD commit after successful commit as a final sanity check. Signed-off-by: Junio C Hamano diff --git a/Documentation/tutorial-2.txt b/Documentation/tutorial-2.txt index 42b6e7d..6389de5 100644 --- a/Documentation/tutorial-2.txt +++ b/Documentation/tutorial-2.txt @@ -23,6 +23,7 @@ $ echo 'hello world' > file.txt $ git add . $ git commit -a -m "initial commit" Committing initial tree 92b8b694ffb1675e5975148e1121810081dbdffe + create mode 100644 file.txt $ echo 'hello world!' >file.txt $ git commit -a -m "add emphasis" ------------------------------------------------ diff --git a/git-commit.sh b/git-commit.sh index 81c3a0c..7e9742d 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -629,4 +629,7 @@ if test -x "$GIT_DIR"/hooks/post-commit && test "$ret" = 0 then "$GIT_DIR"/hooks/post-commit fi + +test "$ret" = 0 && git-diff-tree --summary --root --no-commit-id HEAD + exit "$ret" -- cgit v0.10.2-6-g49f6 From f64d7fd267c501f501e18a888e3e1e0c5b56458f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 25 Nov 2006 01:04:28 -0800 Subject: git-fetch: exit with non-zero status when fast-forward check fails When update_local_ref() refuses to update a branch head due to fast-forward check, it was not propagated properly in the call chain and the command did not exit with non-zero status as a result. Signed-off-by: Junio C Hamano diff --git a/git-fetch.sh b/git-fetch.sh index eb32476..4425562 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -359,7 +359,7 @@ fetch_main () { esac append_fetch_head "$head" "$remote" \ - "$remote_name" "$remote_nick" "$local_name" "$not_for_merge" + "$remote_name" "$remote_nick" "$local_name" "$not_for_merge" || exit done @@ -413,15 +413,16 @@ fetch_main () { done local_name=$(expr "z$found" : 'z[^:]*:\(.*\)') append_fetch_head "$sha1" "$remote" \ - "$remote_name" "$remote_nick" "$local_name" "$not_for_merge" - done + "$remote_name" "$remote_nick" "$local_name" \ + "$not_for_merge" || exit + done && if [ "$pack_lockfile" ]; then rm -f "$pack_lockfile"; fi ) || exit ;; esac } -fetch_main "$reflist" +fetch_main "$reflist" || exit # automated tag following case "$no_tags$tags" in @@ -449,7 +450,7 @@ case "$no_tags$tags" in case "$taglist" in '') ;; ?*) - fetch_main "$taglist" ;; + fetch_main "$taglist" || exit ;; esac esac -- cgit v0.10.2-6-g49f6 From d945d4be20d577868646f1b676b605cd9fdadf86 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 25 Nov 2006 01:10:10 -0800 Subject: git-fetch: allow forcing glob pattern in refspec Building on top of the earlier refspec glob pattern enhancement, this allows a glob pattern to say the updates should be forced by prefixing it with '+' as usual, like this: Pull: +refs/heads/*:refs/remotes/origin/* Signed-off-by: Junio C Hamano diff --git a/git-parse-remote.sh b/git-parse-remote.sh index e281b7c..19bc385 100755 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -99,13 +99,17 @@ get_remote_default_refs_for_push () { expand_refs_wildcard () { for ref do + lref=${ref#'+'} # a non glob pattern is given back as-is. - expr "z$ref" : 'zrefs/.*/\*:refs/.*/\*$' >/dev/null || { + expr "z$lref" : 'zrefs/.*/\*:refs/.*/\*$' >/dev/null || { echo "$ref" continue } - from=`expr "z$ref" : 'z\(refs/.*/\)\*:refs/.*/\*$'` - to=`expr "z$ref" : 'zrefs/.*/\*:\(refs/.*/\)\*$'` + + from=`expr "z$lref" : 'z\(refs/.*/\)\*:refs/.*/\*$'` + to=`expr "z$lref" : 'zrefs/.*/\*:\(refs/.*/\)\*$'` + local_force= + test "z$lref" = "z$ref" || local_force='+' echo "$ls_remote_result" | ( IFS=' ' @@ -117,7 +121,7 @@ expand_refs_wildcard () { then continue fi - echo "${name}:${to}${mapped}" + echo "${local_force}${name}:${to}${mapped}" done ) done -- cgit v0.10.2-6-g49f6 From 310b86d48091ebb6a71782678769b2cb8fe2ecd5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 25 Nov 2006 01:33:06 -0800 Subject: fetch-pack: do not barf when duplicate re patterns are given Signed-off-by: Junio C Hamano diff --git a/fetch-pack.c b/fetch-pack.c index 0a169dc..743eab7 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -566,6 +566,29 @@ static int fetch_pack(int fd[2], int nr_match, char **match) return 0; } +static int remove_duplicates(int nr_heads, char **heads) +{ + int src, dst; + + for (src = dst = 0; src < nr_heads; src++) { + /* If heads[src] is different from any of + * heads[0..dst], push it in. + */ + int i; + for (i = 0; i < dst; i++) { + if (!strcmp(heads[i], heads[src])) + break; + } + if (i < dst) + continue; + if (src != dst) + heads[dst] = heads[src]; + dst++; + } + heads[dst] = 0; + return dst; +} + int main(int argc, char **argv) { int i, ret, nr_heads; @@ -617,6 +640,8 @@ int main(int argc, char **argv) pid = git_connect(fd, dest, exec); if (pid < 0) return 1; + if (heads && nr_heads) + nr_heads = remove_duplicates(nr_heads, heads); ret = fetch_pack(fd, nr_heads, heads); close(fd[0]); close(fd[1]); -- cgit v0.10.2-6-g49f6 From 391862e34571c0e7e88a5f6e84211b7b8bf55440 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 25 Nov 2006 09:43:59 +0100 Subject: gitweb: Do not use esc_html in esc_path Do not use esc_html in esc_path subroutine to avoid double quoting; expand esc_html body (except quoting) in esc_path. Move esc_path before quot_cec and quot_upr. Add some comments. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 6ae7e80..38c9437 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -585,7 +585,21 @@ sub esc_html ($;%) { return $str; } -# Make control characterss "printable". +# quote control characters and escape filename to HTML +sub esc_path { + my $str = shift; + my %opts = @_; + + $str = to_utf8($str); + $str = escapeHTML($str); + if ($opts{'-nbsp'}) { + $str =~ s/ / /g; + } + $str =~ s|([[:cntrl:]])|quot_cec($1)|eg; + return $str; +} + +# Make control characters "printable", using character escape codes (CEC) sub quot_cec { my $cntrl = shift; my %es = ( # character escape codes, aka escape sequences @@ -605,22 +619,14 @@ sub quot_cec { return "$chr"; } -# Alternatively use unicode control pictures codepoints. +# Alternatively use unicode control pictures codepoints, +# Unicode "printable representation" (PR) sub quot_upr { my $cntrl = shift; my $chr = sprintf('&#%04d;', 0x2400+ord($cntrl)); return "$chr"; } -# quote control characters and escape filename to HTML -sub esc_path { - my $str = shift; - - $str = esc_html($str); - $str =~ s|([[:cntrl:]])|quot_cec($1)|eg; - return $str; -} - # git may return quoted and escaped filenames sub unquote { my $str = shift; -- cgit v0.10.2-6-g49f6 From 28b9d9f7c67cfd199c4bc9e1ac5197cb17349b15 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 25 Nov 2006 11:32:08 +0100 Subject: gitweb: Use git-show-ref instead of git-peek-remote Use "git show-ref --dereference" instead of "git peek-remote $projectroot/project" in git_get_references. git-show-ref is faster than git-peek-remote (40ms vs 56ms user+sys for git.git repository); even faster is reading info/refs file (if it exists), but the information in info/refs can be stale; that and the fact that info/refs is meant for dumb protocol transports, not for gitweb. git-show-ref is available since v1.4.4; the output format is slightly different than git-peek-remote output format, but we accept both. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 38c9437..26fc3a6 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1154,14 +1154,15 @@ sub git_get_last_activity { sub git_get_references { my $type = shift || ""; my %refs; - # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11 - # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{} - open my $fd, "-|", $GIT, "peek-remote", "$projectroot/$project/" + # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11 + # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{} + open my $fd, "-|", git_cmd(), "show-ref", "--dereference", + ($type ? ("--", "refs/$type") : ()) # use -- if $type or return; while (my $line = <$fd>) { chomp $line; - if ($line =~ m/^([0-9a-fA-F]{40})\trefs\/($type\/?[^\^]+)/) { + if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type/?[^^]+)!) { if (defined $refs{$1}) { push @{$refs{$1}}, $2; } else { -- cgit v0.10.2-6-g49f6 From ba00b8c1edafcc414cfe13f8a4addac3893c2a29 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 25 Nov 2006 15:54:32 +0100 Subject: gitweb: Add author and committer email extraction to parse_commit Extract author email to 'author_email' key, and comitter mail to 'committer_mail' key; uniquify committer and author lines handling by the way. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 26fc3a6..85a896b 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1294,8 +1294,9 @@ sub parse_commit { $co{'author'} = $1; $co{'author_epoch'} = $2; $co{'author_tz'} = $3; - if ($co{'author'} =~ m/^([^<]+) ]*)>/) { + $co{'author_name'} = $1; + $co{'author_email'} = $2; } else { $co{'author_name'} = $co{'author'}; } @@ -1304,7 +1305,12 @@ sub parse_commit { $co{'committer_epoch'} = $2; $co{'committer_tz'} = $3; $co{'committer_name'} = $co{'committer'}; - $co{'committer_name'} =~ s/ <.*//; + if ($co{'committer'} =~ m/^([^<]+) <([^>]*)>/) { + $co{'committer_name'} = $1; + $co{'committer_email'} = $2; + } else { + $co{'committer_name'} = $co{'committer'}; + } } } if (!defined $co{'tree'}) { -- cgit v0.10.2-6-g49f6 From ab23c19d67d283567fdf18966e347a78ade56c22 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 25 Nov 2006 15:54:33 +0100 Subject: gitweb: Add author and contributor email to Atom feed Add author email (from 'author_email') and contributor email (from 'committer_email') to items in the Atom format gitweb feed. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 85a896b..fb7026d 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -4323,9 +4323,19 @@ XML print "\n" . "" . esc_html($co{'title'}) . "\n" . "$cd{'iso-8601'}\n" . - "" . esc_html($co{'author_name'}) . "\n" . + "\n" . + " " . esc_html($co{'author_name'}) . "\n"; + if ($co{'author_email'}) { + print " " . esc_html($co{'author_email'}) . "\n"; + } + print "\n" . # use committer for contributor - "" . esc_html($co{'committer_name'}) . "\n" . + "\n" . + " " . esc_html($co{'committer_name'}) . "\n"; + if ($co{'committer_email'}) { + print " " . esc_html($co{'committer_email'}) . "\n"; + } + print "\n" . "$cd{'iso-8601'}\n" . "\n" . "$co_url\n" . -- cgit v0.10.2-6-g49f6 From 91fd2bf3fdc72351532a8fd74cdd0da37b036ed1 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 25 Nov 2006 15:54:34 +0100 Subject: gitweb: Use author_epoch for pubdate in gitweb feeds Use creation date (author_epoch) instead of former commit date (committer_epoch) as publish date in gitweb feeds (RSS, Atom). Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index fb7026d..1c5b854 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -4201,7 +4201,7 @@ sub git_feed { } if (defined($revlist[0])) { %latest_commit = parse_commit($revlist[0]); - %latest_date = parse_date($latest_commit{'committer_epoch'}); + %latest_date = parse_date($latest_commit{'author_epoch'}); print $cgi->header( -type => $content_type, -charset => 'utf-8', @@ -4294,10 +4294,10 @@ XML my $commit = $revlist[$i]; my %co = parse_commit($commit); # we read 150, we always show 30 and the ones more recent than 48 hours - if (($i >= 20) && ((time - $co{'committer_epoch'}) > 48*60*60)) { + if (($i >= 20) && ((time - $co{'author_epoch'}) > 48*60*60)) { last; } - my %cd = parse_date($co{'committer_epoch'}); + my %cd = parse_date($co{'author_epoch'}); # get list of changed files open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, -- cgit v0.10.2-6-g49f6 From efe4631def181d32f932672a7ea31e52ee0ab308 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 25 Nov 2006 17:38:41 -0800 Subject: git-svn: allow SVN:: lib users to track the root of the repository (again) I broke this again in 747fa12cef73b6ca04fffaddaad7326cf546cdea. Thanks to merlyn for pointing this out to me on IRC. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index 0a47b1f..de4e74a 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2919,8 +2919,12 @@ sub libsvn_fetch { my $p = $SVN->{svn_path}; foreach my $f (keys %$paths) { my $m = $paths->{$f}->action(); - $f =~ s#^/\Q$p\E/##; - next if $f =~ m#^/#; + if (length $p) { + $f =~ s#^/\Q$p\E/##; + next if $f =~ m#^/#; + } else { + $f =~ s#^/##; + } if ($m =~ /^[DR]$/) { print "\t$m\t$f\n" unless $_q; process_rm($gui, $last_commit, $f); -- cgit v0.10.2-6-g49f6 From e88ce8a45656f750551ee21abf3be5576f6b0be4 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 26 Nov 2006 02:18:26 +0100 Subject: gitweb: Make project description in projects list link to summary view Make (shortened) project description in the "projects list" view hyperlink to the "summary" view of the project. Project names are sometimes short; having project description be hyperling gives larger are to click. While at it, display full description on mouseover via 'title' attribute to introduced link. Additionally, fix whitespace usage in modified git_project_list_body subroutine: tabs are for indent, spaces are for align. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 1c5b854..093bd72 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2441,6 +2441,7 @@ sub git_project_list_body { ($pr->{'age'}, $pr->{'age_string'}) = @aa; if (!defined $pr->{'descr'}) { my $descr = git_get_project_description($pr->{'path'}) || ""; + $pr->{'descr_long'} = to_utf8($descr); $pr->{'descr'} = chop_str($descr, 25, 5); } if (!defined $pr->{'owner'}) { @@ -2476,7 +2477,7 @@ sub git_project_list_body { } else { print "" . $cgi->a({-href => href(project=>undef, order=>'project'), - -class => "header"}, "Project") . + -class => "header"}, "Project") . "\n"; } if ($order eq "descr") { @@ -2485,7 +2486,7 @@ sub git_project_list_body { } else { print "" . $cgi->a({-href => href(project=>undef, order=>'descr'), - -class => "header"}, "Description") . + -class => "header"}, "Description") . "\n"; } if ($order eq "owner") { @@ -2494,7 +2495,7 @@ sub git_project_list_body { } else { print "" . $cgi->a({-href => href(project=>undef, order=>'owner'), - -class => "header"}, "Owner") . + -class => "header"}, "Owner") . "\n"; } if ($order eq "age") { @@ -2503,7 +2504,7 @@ sub git_project_list_body { } else { print "" . $cgi->a({-href => href(project=>undef, order=>'age'), - -class => "header"}, "Last Change") . + -class => "header"}, "Last Change") . "\n"; } print "\n" . @@ -2528,7 +2529,9 @@ sub git_project_list_body { } print "" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"), -class => "list"}, esc_html($pr->{'path'})) . "\n" . - "" . esc_html($pr->{'descr'}) . "\n" . + "" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"), + -class => "list", -title => $pr->{'descr_long'}}, + esc_html($pr->{'descr'})) . "\n" . "" . chop_str($pr->{'owner'}, 15) . "\n"; print "{'age'}) . "\">" . $pr->{'age_string'} . "\n" . -- cgit v0.10.2-6-g49f6 From 93ee7823c0a48b7a51ac0e145b5bcc450c805ea4 Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Sat, 25 Nov 2006 22:45:02 -0500 Subject: Documentation: clarify tutorial pull/merge discussion Attempt to clarify somewhat the difference between pull and merge, and give a little more details on the pull syntax. I'm still not happy with this section: the explanation of the origin branch isn't great, but maybe that should be left alone pending the use-separate-remotes change; and we need to explain how to set up a public repository and push to it. Signed-off-by: J. Bruce Fields Signed-off-by: Junio C Hamano diff --git a/Documentation/tutorial.txt b/Documentation/tutorial.txt index 1e4ddfb..35af81a 100644 --- a/Documentation/tutorial.txt +++ b/Documentation/tutorial.txt @@ -209,29 +209,28 @@ at /home/bob/myrepo. She does this with: ------------------------------------------------ $ cd /home/alice/project -$ git pull /home/bob/myrepo +$ git pull /home/bob/myrepo master ------------------------------------------------ -This actually pulls changes from the branch in Bob's repository named -"master". Alice could request a different branch by adding the name -of the branch to the end of the git pull command line. +This merges the changes from Bob's "master" branch into Alice's +current branch. If Alice has made her own changes in the meantime, +then she may need to manually fix any conflicts. (Note that the +"master" argument in the above command is actually unnecessary, as it +is the default.) -This merges Bob's changes into her repository; "git log" will -now show the new commits. If Alice has made her own changes in the -meantime, then Bob's changes will be merged in, and she will need to -manually fix any conflicts. +The "pull" command thus performs two operations: it fetches changes +from a remote branch, then merges them into the current branch. -A more cautious Alice might wish to examine Bob's changes before -pulling them. She can do this by creating a temporary branch just -for the purpose of studying Bob's changes: +You can perform the first operation alone using the "git fetch" +command. For example, Alice could create a temporary branch just to +track Bob's changes, without merging them with her own, using: ------------------------------------- $ git fetch /home/bob/myrepo master:bob-incoming ------------------------------------- which fetches the changes from Bob's master branch into a new branch -named bob-incoming. (Unlike git pull, git fetch just fetches a copy -of Bob's line of development without doing any merging). Then +named bob-incoming. Then ------------------------------------- $ git log -p master..bob-incoming @@ -240,8 +239,8 @@ $ git log -p master..bob-incoming shows a list of all the changes that Bob made since he branched from Alice's master branch. -After examining those changes, and possibly fixing things, Alice can -pull the changes into her master branch: +After examining those changes, and possibly fixing things, Alice +could pull the changes into her master branch: ------------------------------------- $ git checkout master @@ -251,6 +250,18 @@ $ git pull . bob-incoming The last command is a pull from the "bob-incoming" branch in Alice's own repository. +Alice could also perform both steps at once with: + +------------------------------------- +$ git pull /home/bob/myrepo master:bob-incoming +------------------------------------- + +This is just like the "git pull /home/bob/myrepo master" that we saw +before, except that it also stores the unmerged changes from bob's +master branch in bob-incoming before merging them into Alice's +current branch. Note that git pull always merges into the current +branch, regardless of what else is given on the commandline. + Later, Bob can update his repo with Alice's latest changes using ------------------------------------- -- cgit v0.10.2-6-g49f6 From aabd76930fdc585dd3e198a3351d426583af4081 Mon Sep 17 00:00:00 2001 From: Han-Wen Nienhuys Date: Sun, 26 Nov 2006 17:42:49 +0100 Subject: git-tag: allow empty tag message if -m is given explicitly. Signed-off-by: Han-Wen Nienhuys Signed-off-by: Junio C Hamano diff --git a/git-tag.sh b/git-tag.sh index ac269e3..d53f94c 100755 --- a/git-tag.sh +++ b/git-tag.sh @@ -5,6 +5,7 @@ USAGE='-l [] | [-a | -s | -u ] [-f | -d] [-m ] [ SUBDIRECTORY_OK='Yes' . git-sh-setup +message_given= annotate= signed= force= @@ -37,6 +38,12 @@ do annotate=1 shift message="$1" + if test "$#" = "0"; then + die "error: option -m needs an argument" + exit 2 + else + message_given=1 + fi ;; -u) annotate=1 @@ -83,7 +90,7 @@ tagger=$(git-var GIT_COMMITTER_IDENT) || exit 1 trap 'rm -f "$GIT_DIR"/TAG_TMP* "$GIT_DIR"/TAG_FINALMSG "$GIT_DIR"/TAG_EDITMSG' 0 if [ "$annotate" ]; then - if [ -z "$message" ]; then + if [ -z "$message_given" ]; then ( echo "#" echo "# Write a tag message" echo "#" ) > "$GIT_DIR"/TAG_EDITMSG @@ -95,7 +102,7 @@ if [ "$annotate" ]; then grep -v '^#' <"$GIT_DIR"/TAG_EDITMSG | git-stripspace >"$GIT_DIR"/TAG_FINALMSG - [ -s "$GIT_DIR"/TAG_FINALMSG ] || { + [ -s "$GIT_DIR"/TAG_FINALMSG -o -n "$message_given" ] || { echo >&2 "No tag message?" exit 1 } -- cgit v0.10.2-6-g49f6 From 36f2587ffb6802cb38071510810f48cddfc4f34a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 26 Nov 2006 12:47:52 -0800 Subject: grep: do not skip unmerged entries when grepping in the working tree. We used to skip unmerged entries, which made sense for grepping in the cached copies, but not for grepping in the working tree. Noticed by Johannes Sixt. Signed-off-by: Junio C Hamano diff --git a/builtin-grep.c b/builtin-grep.c index ad7dc00..9873e3d 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -268,7 +268,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached) for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; char *name; - if (ce_stage(ce) || !S_ISREG(ntohl(ce->ce_mode))) + if (!S_ISREG(ntohl(ce->ce_mode))) continue; if (!pathspec_matches(paths, ce->name)) continue; @@ -280,12 +280,19 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached) memcpy(name + 2, ce->name, len + 1); } argv[argc++] = name; - if (argc < MAXARGS) + if (argc < MAXARGS && !ce_stage(ce)) continue; status = exec_grep(argc, argv); if (0 < status) hit = 1; argc = nr; + if (ce_stage(ce)) { + do { + i++; + } while (i < active_nr && + !strcmp(ce->name, active_cache[i]->name)); + i--; /* compensate for loop control */ + } } if (argc > nr) { status = exec_grep(argc, argv); @@ -316,14 +323,24 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached) for (nr = 0; nr < active_nr; nr++) { struct cache_entry *ce = active_cache[nr]; - if (ce_stage(ce) || !S_ISREG(ntohl(ce->ce_mode))) + if (!S_ISREG(ntohl(ce->ce_mode))) continue; if (!pathspec_matches(paths, ce->name)) continue; - if (cached) + if (cached) { + if (ce_stage(ce)) + continue; hit |= grep_sha1(opt, ce->sha1, ce->name, 0); + } else hit |= grep_file(opt, ce->name); + if (ce_stage(ce)) { + do { + nr++; + } while (nr < active_nr && + !strcmp(ce->name, active_cache[nr]->name)); + nr--; /* compensate for loop control */ + } } free_grep_patterns(opt); return hit; -- cgit v0.10.2-6-g49f6 From 51901e96bf592877f7b266d0d9c6cb83b363edce Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 26 Nov 2006 22:16:31 -0800 Subject: git-merge: tighten error checking. If a branch name to be merged is misspelled, the command leaked error messages from underlying plumbing commands, which were helpful only to people who know how the command are implemented to diagnose the breakage, but simply puzzling and unhelpful for the end users. Signed-off-by: Junio C Hamano diff --git a/git-merge.sh b/git-merge.sh index dd4e83d..5fa8b0d 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -188,13 +188,13 @@ else # in this loop. merge_name=$(for remote do - rh=$(git-rev-parse --verify "$remote"^0 2>/dev/null) + rh=$(git-rev-parse --verify "$remote"^0 2>/dev/null) && if git show-ref -q --verify "refs/heads/$remote" then what=branch else what=commit - fi + fi && echo "$rh $what '$remote'" done | git-fmt-merge-msg ) @@ -209,7 +209,7 @@ test "$rloga" = '' && rloga="merge: $@" remoteheads= for remote do - remotehead=$(git-rev-parse --verify "$remote"^0) || + remotehead=$(git-rev-parse --verify "$remote"^0 2>/dev/null) || die "$remote - not something we can merge" remoteheads="${remoteheads}$remotehead " done -- cgit v0.10.2-6-g49f6 From c1751616381b6e8c8eae311107b5969a0233a6d8 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 26 Nov 2006 22:19:42 -0800 Subject: git-merge: do not leak rev-parse output used for checking internally. Signed-off-by: Junio C Hamano diff --git a/git-merge.sh b/git-merge.sh index 5fa8b0d..75af10d 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -159,7 +159,7 @@ then shift head_arg="$1" shift -elif ! git-rev-parse --verify HEAD 2>/dev/null +elif ! git-rev-parse --verify HEAD >/dev/null 2>&1 then # If the merged head is a valid one there is no reason to # forbid "git merge" into a branch yet to be born. We do -- cgit v0.10.2-6-g49f6 From fde97d8ac63efa466dd48436d2178bd364b846de Mon Sep 17 00:00:00 2001 From: Sean Date: Mon, 27 Nov 2006 14:37:43 -0500 Subject: Update documentation to remove incorrect GIT_DIFF_OPTS example. Git no longer calls an external diff program to generate patches. Remove the documentation which suggests that you can pass arbitrary diff options via the GIT_DIFF_OPTS environment variable. Signed-off-by: Sean Estabrooks Signed-off-by: Junio C Hamano diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt index e4520e2..883c1bb 100644 --- a/Documentation/diff-format.txt +++ b/Documentation/diff-format.txt @@ -65,62 +65,17 @@ Generating patches with -p When "git-diff-index", "git-diff-tree", or "git-diff-files" are run with a '-p' option, they do not produce the output described above; -instead they produce a patch file. +instead they produce a patch file. You can customize the creation +of such patches via the GIT_EXTERNAL_DIFF and the GIT_DIFF_OPTS +environment variables. -The patch generation can be customized at two levels. - -1. When the environment variable 'GIT_EXTERNAL_DIFF' is not set, - these commands internally invoke "diff" like this: - - diff -L a/ -L b/ -pu -+ -For added files, `/dev/null` is used for . For removed -files, `/dev/null` is used for -+ -The "diff" formatting options can be customized via the -environment variable 'GIT_DIFF_OPTS'. For example, if you -prefer context diff: - - GIT_DIFF_OPTS=-c git-diff-index -p HEAD - - -2. When the environment variable 'GIT_EXTERNAL_DIFF' is set, the - program named by it is called, instead of the diff invocation - described above. -+ -For a path that is added, removed, or modified, -'GIT_EXTERNAL_DIFF' is called with 7 parameters: - - path old-file old-hex old-mode new-file new-hex new-mode -+ -where: - - -file:: are files GIT_EXTERNAL_DIFF can use to read the - contents of , - -hex:: are the 40-hexdigit SHA1 hashes, - -mode:: are the octal representation of the file modes. - -+ -The file parameters can point at the user's working file -(e.g. `new-file` in "git-diff-files"), `/dev/null` (e.g. `old-file` -when a new file is added), or a temporary file (e.g. `old-file` in the -index). 'GIT_EXTERNAL_DIFF' should not worry about unlinking the -temporary file --- it is removed when 'GIT_EXTERNAL_DIFF' exits. - -For a path that is unmerged, 'GIT_EXTERNAL_DIFF' is called with 1 -parameter, . - - -git specific extension to diff format -------------------------------------- - -What -p option produces is slightly different from the -traditional diff format. +What the -p option produces is slightly different from the traditional +diff format. 1. It is preceded with a "git diff" header, that looks like this: - diff --git a/file1 b/file2 + diff --git a/file1 b/file2 + The `a/` and `b/` filenames are the same unless rename/copy is involved. Especially, even for a creation or a deletion, diff --git a/Documentation/git.txt b/Documentation/git.txt index 619d656..6382ef0 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -639,11 +639,35 @@ git Commits git Diffs ~~~~~~~~~ 'GIT_DIFF_OPTS':: + Only valid setting is "--unified=??" or "-u??" to set the + number of context lines shown when a unified diff is created. + This takes precedence over any "-U" or "--unified" option + value passed on the git diff command line. + 'GIT_EXTERNAL_DIFF':: - see the "generating patches" section in : - gitlink:git-diff-index[1]; - gitlink:git-diff-files[1]; - gitlink:git-diff-tree[1] + When the environment variable 'GIT_EXTERNAL_DIFF' is set, the + program named by it is called, instead of the diff invocation + described above. For a path that is added, removed, or modified, + 'GIT_EXTERNAL_DIFF' is called with 7 parameters: + + path old-file old-hex old-mode new-file new-hex new-mode ++ +where: + + -file:: are files GIT_EXTERNAL_DIFF can use to read the + contents of , + -hex:: are the 40-hexdigit SHA1 hashes, + -mode:: are the octal representation of the file modes. + ++ +The file parameters can point at the user's working file +(e.g. `new-file` in "git-diff-files"), `/dev/null` (e.g. `old-file` +when a new file is added), or a temporary file (e.g. `old-file` in the +index). 'GIT_EXTERNAL_DIFF' should not worry about unlinking the +temporary file --- it is removed when 'GIT_EXTERNAL_DIFF' exits. ++ +For a path that is unmerged, 'GIT_EXTERNAL_DIFF' is called with 1 +parameter, . other ~~~~~ -- cgit v0.10.2-6-g49f6 From 4ad91321ee95598c2488ab5e39afec13575d4e3f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 27 Nov 2006 03:40:47 -0500 Subject: Teach git-completion.bash how to complete git-merge. Now that git-merge is high-level Porcelain users are going to expect to be able to use it from the command line, in which case we really should also be able to complete ref names as parameters. I'm also including completion support for the merge strategies that are supported by git-merge.sh, should the user wish to use a different strategy than their default. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index a43a177..28bd0e3 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -81,6 +81,16 @@ __git_remotes () done } +__git_merge_strategies () +{ + sed -n "/^all_strategies='/{ + s/^all_strategies='// + s/'// + p + q + }" "$(git --exec-path)/git-merge" +} + __git_complete_file () { local pfx ls ref cur="${COMP_WORDS[COMP_CWORD]}" @@ -240,6 +250,24 @@ _git_log () esac } +_git_merge () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + --*) + COMPREPLY=($(compgen -W " + --no-commit --no-summary --squash + " -- "$cur")) + return + esac + if [ $COMP_CWORD -gt 1 -a X-s = "X${COMP_WORDS[COMP_CWORD-1]}" ] + then + COMPREPLY=($(compgen -W "$(__git_merge_strategies)" -- "$cur")) + else + COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) + fi +} + _git_merge_base () { local cur="${COMP_WORDS[COMP_CWORD]}" @@ -348,6 +376,7 @@ _git () log) _git_log ;; ls-remote) _git_ls_remote ;; ls-tree) _git_ls_tree ;; + merge) _git_merge;; merge-base) _git_merge_base ;; pull) _git_pull ;; push) _git_push ;; @@ -376,6 +405,7 @@ complete -o default -o nospace -F _git_fetch git-fetch complete -o default -o nospace -F _git_log git-log complete -o default -F _git_ls_remote git-ls-remote complete -o default -o nospace -F _git_ls_tree git-ls-tree +complete -o default -F _git_merge git-merge complete -o default -F _git_merge_base git-merge-base complete -o default -o nospace -F _git_pull git-pull complete -o default -o nospace -F _git_push git-push -- cgit v0.10.2-6-g49f6 From f2bb9f88805f370b9de1c116f42e3ceb30321c80 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 27 Nov 2006 03:41:01 -0500 Subject: Hide plumbing/transport commands from bash completion. Users generally are not going to need to invoke plumbing-level commands from within one line shell commands. If they are invoking these commands then it is likely that they are glueing them together into a shell script to perform an action, in which case bash completion for these commands is of relatively little use. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 28bd0e3..b55431f 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -125,6 +125,58 @@ __git_complete_file () esac } +__git_commands () +{ + local i IFS=" "$'\n' + for i in $(git help -a|egrep '^ ') + do + case $i in + check-ref-format) : plumbing;; + commit-tree) : plumbing;; + convert-objects) : plumbing;; + cvsserver) : daemon;; + daemon) : daemon;; + fetch-pack) : plumbing;; + hash-object) : plumbing;; + http-*) : transport;; + index-pack) : plumbing;; + local-fetch) : plumbing;; + mailinfo) : plumbing;; + mailsplit) : plumbing;; + merge-*) : plumbing;; + mktree) : plumbing;; + mktag) : plumbing;; + pack-objects) : plumbing;; + pack-redundant) : plumbing;; + pack-refs) : plumbing;; + parse-remote) : plumbing;; + patch-id) : plumbing;; + peek-remote) : plumbing;; + read-tree) : plumbing;; + receive-pack) : plumbing;; + rerere) : plumbing;; + rev-list) : plumbing;; + rev-parse) : plumbing;; + runstatus) : plumbing;; + sh-setup) : internal;; + shell) : daemon;; + send-pack) : plumbing;; + show-index) : plumbing;; + ssh-*) : transport;; + stripspace) : plumbing;; + symbolic-ref) : plumbing;; + unpack-file) : plumbing;; + unpack-objects) : plumbing;; + update-ref) : plumbing;; + update-server-info) : daemon;; + upload-archive) : plumbing;; + upload-pack) : plumbing;; + write-tree) : plumbing;; + *) echo $i;; + esac + done +} + __git_aliases () { local i IFS=$'\n' @@ -355,11 +407,11 @@ _git () done if [ $c -eq $COMP_CWORD -a -z "$command" ]; then - COMPREPLY=($(compgen \ - -W "--git-dir= --version \ - $(git help -a|egrep '^ ') \ - $(__git_aliases)" \ - -- "${COMP_WORDS[COMP_CWORD]}")) + COMPREPLY=($(compgen -W " + --git-dir= --version --exec-path + $(__git_commands) + $(__git_aliases) + " -- "${COMP_WORDS[COMP_CWORD]}")) return; fi -- cgit v0.10.2-6-g49f6 From d33909bf6e78e6ab8a45bfdd19bc19213fde5501 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 27 Nov 2006 03:41:12 -0500 Subject: Teach bash how to complete options for git-name-rev. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index b55431f..1dfb592 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -326,6 +326,12 @@ _git_merge_base () COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) } +_git_name_rev () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + COMPREPLY=($(compgen -W "--tags --all --stdin" -- "$cur")) +} + _git_pull () { local cur="${COMP_WORDS[COMP_CWORD]}" @@ -430,6 +436,7 @@ _git () ls-tree) _git_ls_tree ;; merge) _git_merge;; merge-base) _git_merge_base ;; + name-rev) _git_name_rev ;; pull) _git_pull ;; push) _git_push ;; reset) _git_reset ;; @@ -459,6 +466,7 @@ complete -o default -F _git_ls_remote git-ls-remote complete -o default -o nospace -F _git_ls_tree git-ls-tree complete -o default -F _git_merge git-merge complete -o default -F _git_merge_base git-merge-base +complete -o default -F _git_name_rev git-name-rev complete -o default -o nospace -F _git_pull git-pull complete -o default -o nospace -F _git_push git-push complete -o default -F _git_reset git-reset @@ -479,6 +487,7 @@ complete -o default -o nospace -F _git_diff_tree git-diff-tree.exe complete -o default -o nospace -F _git_log git-log.exe complete -o default -o nospace -F _git_ls_tree git-ls-tree.exe complete -o default -F _git_merge_base git-merge-base.exe +complete -o default -F _git_name_rev git-name-rev.exe complete -o default -o nospace -F _git_push git-push.exe complete -o default -o nospace -F _git_log git-show-branch.exe complete -o default -o nospace -F _git_log git-whatchanged.exe -- cgit v0.10.2-6-g49f6 From d3d717a4ad0c8d7329e79f7d0313baec57c6b585 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 27 Nov 2006 03:41:28 -0500 Subject: Add current branch in PS1 support to git-completion.bash. Many users want to display the current branch name of the current git repository as part of their PS1 prompt, much as their PS1 prompt might also display the current working directory name. We don't force our own PS1 onto the user. Instead we let them craft their own PS1 string and offer them the function __git_ps1 which they can invoke to obtain either "" (when not in a git repository) or "(%s)" where %s is the name of the current branch, as read from HEAD, with the leading refs/heads/ removed. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 1dfb592..a740d05 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -18,12 +18,31 @@ # 2) Added the following line to your .bashrc: # source ~/.git-completion.sh # +# 3) Consider changing your PS1 to also show the current branch: +# PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ ' +# +# The argument to __git_ps1 will be displayed only if you +# are currently in a git repository. The %s token will be +# the name of the current branch. +# __gitdir () { echo "${__git_dir:-$(git rev-parse --git-dir 2>/dev/null)}" } +__git_ps1 () +{ + local b="$(git symbolic-ref HEAD 2>/dev/null)" + if [ -n "$b" ]; then + if [ -n "$1" ]; then + printf "$1" "${b##refs/heads/}" + else + printf " (%s)" "${b##refs/heads/}" + fi + fi +} + __git_refs () { local cmd i is_hash=y dir="${1:-$(__gitdir)}" -- cgit v0.10.2-6-g49f6 From f53352fbaf2bcc6d209388bb2a68d888ceb8014e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 27 Nov 2006 03:41:43 -0500 Subject: Teach bash how to complete git-format-patch. Provide completion for currently known long options supported by git-format-patch as well as the revision list specification argument, which is generally either a refname or in the form a..b. Since _git_log was the only code that knew how to complete a..b, but we want to start adding option support to _git_log also refactor the a..b completion logic out into its own function. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index a740d05..dfdc396 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -144,6 +144,26 @@ __git_complete_file () esac } +__git_complete_revlist () +{ + local pfx cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + *...*) + pfx="${cur%...*}..." + cur="${cur#*...}" + COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur")) + ;; + *..*) + pfx="${cur%..*}.." + cur="${cur#*..}" + COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur")) + ;; + *) + COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) + ;; + esac +} + __git_commands () { local i IFS=" "$'\n' @@ -290,6 +310,26 @@ _git_fetch () esac } +_git_format_patch () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + --*) + COMPREPLY=($(compgen -W " + --stdout --attach --thread + --output-directory + --numbered --start-number + --keep-subject + --signoff + --in-reply-to= + --full-index --binary + " -- "$cur")) + return + ;; + esac + __git_complete_revlist +} + _git_ls_remote () { local cur="${COMP_WORDS[COMP_CWORD]}" @@ -303,22 +343,7 @@ _git_ls_tree () _git_log () { - local pfx cur="${COMP_WORDS[COMP_CWORD]}" - case "$cur" in - *...*) - pfx="${cur%...*}..." - cur="${cur#*...}" - COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur")) - ;; - *..*) - pfx="${cur%..*}.." - cur="${cur#*..}" - COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur")) - ;; - *) - COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) - ;; - esac + __git_complete_revlist } _git_merge () @@ -450,6 +475,7 @@ _git () diff) _git_diff ;; diff-tree) _git_diff_tree ;; fetch) _git_fetch ;; + format-patch) _git_format_patch ;; log) _git_log ;; ls-remote) _git_ls_remote ;; ls-tree) _git_ls_tree ;; @@ -480,6 +506,7 @@ complete -o default -F _git_checkout git-checkout complete -o default -o nospace -F _git_diff git-diff complete -o default -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_log git-log complete -o default -F _git_ls_remote git-ls-remote complete -o default -o nospace -F _git_ls_tree git-ls-tree @@ -503,6 +530,7 @@ complete -o default -F _git_branch git-branch.exe complete -o default -o nospace -F _git_cat_file git-cat-file.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 complete -o default -o nospace -F _git_log git-log.exe complete -o default -o nospace -F _git_ls_tree git-ls-tree.exe complete -o default -F _git_merge_base git-merge-base.exe -- cgit v0.10.2-6-g49f6 From 1273231ee9c7a576a3654d8f2ba77267393564ab Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 27 Nov 2006 03:41:59 -0500 Subject: Teach bash how to complete git-cherry-pick. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index dfdc396..5582561 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -269,6 +269,21 @@ _git_checkout () COMPREPLY=($(compgen -W "-l -b $(__git_refs)" -- "$cur")) } +_git_cherry_pick () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + --*) + COMPREPLY=($(compgen -W " + --edit --no-commit + " -- "$cur")) + ;; + *) + COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) + ;; + esac +} + _git_diff () { __git_complete_file @@ -472,6 +487,7 @@ _git () branch) _git_branch ;; cat-file) _git_cat_file ;; checkout) _git_checkout ;; + cherry-pick) _git_cherry_pick ;; diff) _git_diff ;; diff-tree) _git_diff_tree ;; fetch) _git_fetch ;; @@ -503,6 +519,7 @@ complete -o default -F _gitk gitk complete -o default -F _git_branch git-branch complete -o default -o nospace -F _git_cat_file git-cat-file complete -o default -F _git_checkout git-checkout +complete -o default -F _git_cherry_pick git-cherry-pick complete -o default -o nospace -F _git_diff git-diff complete -o default -F _git_diff_tree git-diff-tree complete -o default -o nospace -F _git_fetch git-fetch -- cgit v0.10.2-6-g49f6 From 61d926a3cdb8f03147580de53e448fc22370cbb1 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 27 Nov 2006 03:42:07 -0500 Subject: Teach bash how to complete git-rebase. As git-rebase is a popular command bash should know how to complete reference names and its long options. We only support completions which make sense given the current state of the repository, that way users don't get shown --continue/--skip/--abort on the first execution. Also added support for long option --strategy to git-merge, as I missed that option earlier and just noticed it while implementing git-rebase. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 5582561..9f63ff6 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -367,16 +367,16 @@ _git_merge () case "$cur" in --*) COMPREPLY=($(compgen -W " - --no-commit --no-summary --squash + --no-commit --no-summary --squash --strategy " -- "$cur")) return esac - if [ $COMP_CWORD -gt 1 -a X-s = "X${COMP_WORDS[COMP_CWORD-1]}" ] - then + case "${COMP_WORDS[COMP_CWORD-1]}" in + -s|--strategy) COMPREPLY=($(compgen -W "$(__git_merge_strategies)" -- "$cur")) - else - COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) - fi + return + esac + COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) } _git_merge_base () @@ -443,6 +443,30 @@ _git_push () esac } +_git_rebase () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + if [ -d .dotest ]; then + COMPREPLY=($(compgen -W " + --continue --skip --abort + " -- "$cur")) + return + fi + case "$cur" in + --*) + COMPREPLY=($(compgen -W " + --onto --merge --strategy + " -- "$cur")) + return + esac + case "${COMP_WORDS[COMP_CWORD-1]}" in + -s|--strategy) + COMPREPLY=($(compgen -W "$(__git_merge_strategies)" -- "$cur")) + return + esac + COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) +} + _git_reset () { local cur="${COMP_WORDS[COMP_CWORD]}" @@ -500,6 +524,7 @@ _git () name-rev) _git_name_rev ;; pull) _git_pull ;; push) _git_push ;; + rebase) _git_rebase ;; reset) _git_reset ;; show) _git_show ;; show-branch) _git_log ;; @@ -532,6 +557,7 @@ complete -o default -F _git_merge_base git-merge-base complete -o default -F _git_name_rev git-name-rev complete -o default -o nospace -F _git_pull git-pull complete -o default -o nospace -F _git_push git-push +complete -o default -F _git_rebase git-rebase complete -o default -F _git_reset git-reset complete -o default -F _git_show git-show complete -o default -o nospace -F _git_log git-show-branch -- cgit v0.10.2-6-g49f6 From 6e31b866e4c8e6fc432e6087d56be73c08cf0f83 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 27 Nov 2006 03:42:18 -0500 Subject: Teach bash about git log/show/whatchanged options. Typing out options to git log/show/whatchanged can take a while, but we can easily complete them with bash. So list the most common ones, especially --pretty=online|short|medium|... so that users don't need to type everything out. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 9f63ff6..3852f46 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -358,6 +358,29 @@ _git_ls_tree () _git_log () { + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + --pretty=*) + COMPREPLY=($(compgen -W " + oneline short medium full fuller email raw + " -- "${cur##--pretty=}")) + return + ;; + --*) + COMPREPLY=($(compgen -W " + --max-count= --max-age= --since= --after= + --min-age= --before= --until= + --root --not --topo-order --date-order + --no-merges + --abbrev-commit --abbrev= + --relative-date + --author= --committer= --grep= + --all-match + --pretty= --name-status --name-only + " -- "$cur")) + return + ;; + esac __git_complete_revlist } @@ -474,12 +497,6 @@ _git_reset () COMPREPLY=($(compgen -W "$opt $(__git_refs)" -- "$cur")) } -_git_show () -{ - local cur="${COMP_WORDS[COMP_CWORD]}" - COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) -} - _git () { local i c=1 command __git_dir @@ -526,7 +543,7 @@ _git () push) _git_push ;; rebase) _git_rebase ;; reset) _git_reset ;; - show) _git_show ;; + show) _git_log ;; show-branch) _git_log ;; whatchanged) _git_log ;; *) COMPREPLY=() ;; @@ -559,7 +576,7 @@ complete -o default -o nospace -F _git_pull git-pull complete -o default -o nospace -F _git_push git-push complete -o default -F _git_rebase git-rebase complete -o default -F _git_reset git-reset -complete -o default -F _git_show git-show +complete -o default -F _git_log git-show complete -o default -o nospace -F _git_log git-show-branch complete -o default -o nospace -F _git_log git-whatchanged @@ -579,6 +596,7 @@ complete -o default -o nospace -F _git_ls_tree git-ls-tree.exe complete -o default -F _git_merge_base git-merge-base.exe complete -o default -F _git_name_rev git-name-rev.exe complete -o default -o nospace -F _git_push git-push.exe +complete -o default -o nospace -F _git_log git-show.exe complete -o default -o nospace -F _git_log git-show-branch.exe complete -o default -o nospace -F _git_log git-whatchanged.exe fi -- cgit v0.10.2-6-g49f6 From 35e65ecca78ceeca8eca72149e7546de94ed8607 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 27 Nov 2006 03:42:32 -0500 Subject: Support bash completion of refs/remote. Now that people are really likely to start using separate remotes (due to the default in git-clone changing) we should support ref completion for these refs in as many commands as possible. While we are working on this routine we should use for-each-ref to obtain a list of local refs, as this should run faster than peek-remote as it does not need to dereference tag objects in order to produce the list of refs back to us. It should also be more friendly to users of StGIT as we won't generate a list of the StGIT metadata refs. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 3852f46..a9c456f 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -47,16 +47,26 @@ __git_refs () { local cmd i is_hash=y dir="${1:-$(__gitdir)}" if [ -d "$dir" ]; then - cmd=git-peek-remote - else - cmd=git-ls-remote + if [ -e "$dir/HEAD" ]; then echo HEAD; fi + for i in $(git --git-dir="$dir" \ + for-each-ref --format='%(refname)' \ + refs/tags refs/heads refs/remotes); do + case "$i" in + refs/tags/*) echo "${i#refs/tags/}" ;; + refs/heads/*) echo "${i#refs/heads/}" ;; + refs/remotes/*) echo "${i#refs/remotes/}" ;; + *) echo "$i" ;; + esac + done + return fi - for i in $($cmd "$dir" 2>/dev/null); do + for i in $(git-ls-remote "$dir" 2>/dev/null); do case "$is_hash,$i" in y,*) is_hash=n ;; n,*^{}) is_hash=y ;; n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}" ;; n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;; + n,refs/remotes/*) is_hash=y; echo "${i#refs/remotes/}" ;; n,*) is_hash=y; echo "$i" ;; esac done -- cgit v0.10.2-6-g49f6 From 5de40f59d4954738448e238b06eed72f73cca740 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 27 Nov 2006 04:44:47 -0500 Subject: Teach bash about git-repo-config. This is a really ugly completion script for git-repo-config, but it has some nice properties. I've added all of the documented configuration parameters from Documentation/config.txt to the script, allowing the user to complete any standard configuration parameter name. We also have some intelligence for the remote.*.* and branch.*.* keys by completing not only the key name (e.g. remote.origin) but also the values (e.g. remote.*.fetch completes to the branches available on the corresponding remote). Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index a9c456f..73c6769 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -43,6 +43,27 @@ __git_ps1 () fi } +__git_heads () +{ + local cmd i is_hash=y dir="${1:-$(__gitdir)}" + if [ -d "$dir" ]; then + for i in $(git --git-dir="$dir" \ + for-each-ref --format='%(refname)' \ + refs/heads ); do + echo "${i#refs/heads/}" + done + return + fi + for i in $(git-ls-remote "$dir" 2>/dev/null); do + case "$is_hash,$i" in + y,*) is_hash=n ;; + n,*^{}) is_hash=y ;; + n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;; + n,*) is_hash=y; echo "$i" ;; + esac + done +} + __git_refs () { local cmd i is_hash=y dir="${1:-$(__gitdir)}" @@ -91,6 +112,23 @@ __git_refs2 () done } +__git_refs_remotes () +{ + local cmd i is_hash=y + for i in $(git-ls-remote "$1" 2>/dev/null); do + case "$is_hash,$i" in + n,refs/heads/*) + is_hash=y + echo "$i:refs/remotes/$1/${i#refs/heads/}" + ;; + y,*) is_hash=n ;; + n,*^{}) is_hash=y ;; + n,refs/tags/*) is_hash=y;; + n,*) is_hash=y; ;; + esac + done +} + __git_remotes () { local i ngoff IFS=$'\n' d="$(__gitdir)" @@ -500,6 +538,119 @@ _git_rebase () COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) } +_git_repo_config () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + local prv="${COMP_WORDS[COMP_CWORD-1]}" + case "$prv" in + branch.*.remote) + COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur")) + return + ;; + branch.*.merge) + COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) + return + ;; + remote.*.fetch) + local remote="${prv#remote.}" + remote="${remote%.fetch}" + COMPREPLY=($(compgen -W "$(__git_refs_remotes "$remote")" \ + -- "$cur")) + return + ;; + remote.*.push) + local remote="${prv#remote.}" + remote="${remote%.push}" + COMPREPLY=($(compgen -W "$(git --git-dir="$(__gitdir)" \ + for-each-ref --format='%(refname):%(refname)' \ + refs/heads)" -- "$cur")) + return + ;; + *.*) + COMPREPLY=() + return + ;; + esac + case "$cur" in + --*) + COMPREPLY=($(compgen -W " + --global --list --replace-all + --get --get-all --get-regexp + --unset --unset-all + " -- "$cur")) + return + ;; + branch.*.*) + local pfx="${cur%.*}." + cur="${cur##*.}" + COMPREPLY=($(compgen -P "$pfx" -W "remote merge" -- "$cur")) + return + ;; + branch.*) + local pfx="${cur%.*}." + cur="${cur#*.}" + COMPREPLY=($(compgen -P "$pfx" -S . \ + -W "$(__git_heads)" -- "$cur")) + return + ;; + remote.*.*) + local pfx="${cur%.*}." + cur="${cur##*.}" + COMPREPLY=($(compgen -P "$pfx" -W "url fetch push" -- "$cur")) + return + ;; + remote.*) + local pfx="${cur%.*}." + cur="${cur#*.}" + COMPREPLY=($(compgen -P "$pfx" -S . \ + -W "$(__git_remotes)" -- "$cur")) + return + ;; + esac + COMPREPLY=($(compgen -W " + apply.whitespace + core.fileMode + core.gitProxy + core.ignoreStat + core.preferSymlinkRefs + core.logAllRefUpdates + core.repositoryFormatVersion + core.sharedRepository + core.warnAmbiguousRefs + core.compression + core.legacyHeaders + i18n.commitEncoding + diff.color + diff.renameLimit + diff.renames + pager.color + status.color + log.showroot + show.difftree + showbranch.default + whatchanged.difftree + http.sslVerify + http.sslCert + http.sslKey + http.sslCAInfo + http.sslCAPath + http.maxRequests + http.lowSpeedLimit http.lowSpeedTime + http.noEPSV + pack.window + repack.useDeltaBaseOffset + pull.octopus pull.twohead + merge.summary + receive.unpackLimit + receive.denyNonFastForwards + user.name user.email + tar.umask + gitcvs.enabled + gitcvs.logfile + branch. remote. + " -- "$cur")) +} + _git_reset () { local cur="${COMP_WORDS[COMP_CWORD]}" @@ -552,6 +703,7 @@ _git () pull) _git_pull ;; push) _git_push ;; rebase) _git_rebase ;; + repo-config) _git_repo_config ;; reset) _git_reset ;; show) _git_log ;; show-branch) _git_log ;; @@ -585,6 +737,7 @@ complete -o default -F _git_name_rev git-name-rev complete -o default -o nospace -F _git_pull git-pull complete -o default -o nospace -F _git_push git-push complete -o default -F _git_rebase git-rebase +complete -o default -F _git_repo_config git-repo-config complete -o default -F _git_reset git-reset complete -o default -F _git_log git-show complete -o default -o nospace -F _git_log git-show-branch @@ -606,6 +759,7 @@ complete -o default -o nospace -F _git_ls_tree git-ls-tree.exe complete -o default -F _git_merge_base git-merge-base.exe complete -o default -F _git_name_rev git-name-rev.exe complete -o default -o nospace -F _git_push git-push.exe +complete -o default -F _git_repo_config git-repo-config complete -o default -o nospace -F _git_log git-show.exe complete -o default -o nospace -F _git_log git-show-branch.exe complete -o default -o nospace -F _git_log git-whatchanged.exe -- cgit v0.10.2-6-g49f6 From ce1e39d29ee373786ceda9e79d0906a6451ab5a5 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 27 Nov 2006 15:10:42 -0500 Subject: Support --strategy=x completion in addition to --strategy x. Because git-merge and git-rebase both accept -s, --strategy or --strategy= we should recognize all three formats in the bash completion functions and issue back all merge strategies on demand. I also moved the prior word testing to be before the current word testing, as the current word cannot be completed with -- if the prior word was an option which requires a parameter, such as -s or --strategy. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 73c6769..16b8dda 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -435,18 +435,23 @@ _git_log () _git_merge () { local cur="${COMP_WORDS[COMP_CWORD]}" + case "${COMP_WORDS[COMP_CWORD-1]}" in + -s|--strategy) + COMPREPLY=($(compgen -W "$(__git_merge_strategies)" -- "$cur")) + return + esac case "$cur" in + --strategy=*) + COMPREPLY=($(compgen -W "$(__git_merge_strategies)" \ + -- "${cur##--strategy=}")) + return + ;; --*) COMPREPLY=($(compgen -W " --no-commit --no-summary --squash --strategy " -- "$cur")) return esac - case "${COMP_WORDS[COMP_CWORD-1]}" in - -s|--strategy) - COMPREPLY=($(compgen -W "$(__git_merge_strategies)" -- "$cur")) - return - esac COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) } @@ -523,18 +528,23 @@ _git_rebase () " -- "$cur")) return fi + case "${COMP_WORDS[COMP_CWORD-1]}" in + -s|--strategy) + COMPREPLY=($(compgen -W "$(__git_merge_strategies)" -- "$cur")) + return + esac case "$cur" in + --strategy=*) + COMPREPLY=($(compgen -W "$(__git_merge_strategies)" \ + -- "${cur##--strategy=}")) + return + ;; --*) COMPREPLY=($(compgen -W " --onto --merge --strategy " -- "$cur")) return esac - case "${COMP_WORDS[COMP_CWORD-1]}" in - -s|--strategy) - COMPREPLY=($(compgen -W "$(__git_merge_strategies)" -- "$cur")) - return - esac COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur")) } -- cgit v0.10.2-6-g49f6 From b51ec6bddb572def384a21a18d5919a488e06ffb Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 27 Nov 2006 15:11:52 -0500 Subject: Cache the list of merge strategies and available commands during load. Since the user's git installation is not likely to grow a new command or merge strategy in the lifespan of the current shell process we can save time during completion operations by caching these lists during sourcing of the completion support. If the git executable is not available or we run into errors while caching at load time then we defer these to runtime and generate the list on the fly. This might happen if the user doesn't put git into their PATH until after the completion script gets sourced. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 16b8dda..902f80b 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -18,7 +18,13 @@ # 2) Added the following line to your .bashrc: # source ~/.git-completion.sh # -# 3) Consider changing your PS1 to also show the current branch: +# 3) You may want to make sure the git executable is available +# in your PATH before this script is sourced, as some caching +# is performed while the script loads. If git isn't found +# at source time then all lookups will be done on demand, +# which may be slightly slower. +# +# 4) Consider changing your PS1 to also show the current branch: # PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ ' # # The argument to __git_ps1 will be displayed only if you @@ -150,6 +156,10 @@ __git_remotes () __git_merge_strategies () { + if [ -n "$__git_merge_strategylist" ]; then + echo "$__git_merge_strategylist" + return + fi sed -n "/^all_strategies='/{ s/^all_strategies='// s/'// @@ -157,6 +167,8 @@ __git_merge_strategies () q }" "$(git --exec-path)/git-merge" } +__git_merge_strategylist= +__git_merge_strategylist="$(__git_merge_strategies 2>/dev/null)" __git_complete_file () { @@ -214,6 +226,10 @@ __git_complete_revlist () __git_commands () { + if [ -n "$__git_commandlist" ]; then + echo "$__git_commandlist" + return + fi local i IFS=" "$'\n' for i in $(git help -a|egrep '^ ') do @@ -263,6 +279,8 @@ __git_commands () esac done } +__git_commandlist= +__git_commandlist="$(__git_commands 2>/dev/null)" __git_aliases () { -- cgit v0.10.2-6-g49f6 From 88329195159865c0fcc57a6017c959d13d7a1ad6 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 27 Nov 2006 15:12:03 -0500 Subject: Teach bash about git-am/git-apply and their whitespace options. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 902f80b..d8ae4d7 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -307,6 +307,54 @@ __git_aliased_command () done } +__git_whitespacelist="nowarn warn error error-all strip" + +_git_am () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + if [ -d .dotest ]; then + COMPREPLY=($(compgen -W " + --skip --resolved + " -- "$cur")) + return + fi + case "$cur" in + --whitespace=*) + COMPREPLY=($(compgen -W "$__git_whitespacelist" \ + -- "${cur##--whitespace=}")) + return + ;; + --*) + COMPREPLY=($(compgen -W " + --signoff --utf8 --binary --3way --interactive + --whitespace= + " -- "$cur")) + return + esac + COMPREPLY=() +} + +_git_apply () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + --whitespace=*) + COMPREPLY=($(compgen -W "$__git_whitespacelist" \ + -- "${cur##--whitespace=}")) + return + ;; + --*) + COMPREPLY=($(compgen -W " + --stat --numstat --summary --check --index + --cached --index-info --reverse --reject --unidiff-zero + --apply --no-add --exclude= + --whitespace= --inaccurate-eof --verbose + " -- "$cur")) + return + esac + COMPREPLY=() +} + _git_branch () { local cur="${COMP_WORDS[COMP_CWORD]}" @@ -714,6 +762,8 @@ _git () [ "$expansion" ] && command="$expansion" case "$command" in + am) _git_am ;; + apply) _git_apply ;; branch) _git_branch ;; cat-file) _git_cat_file ;; checkout) _git_checkout ;; @@ -748,6 +798,8 @@ _gitk () complete -o default -o nospace -F _git git complete -o default -F _gitk gitk +complete -o default -F _git_am git-am +complete -o default -F _git_apply git-apply complete -o default -F _git_branch git-branch complete -o default -o nospace -F _git_cat_file git-cat-file complete -o default -F _git_checkout git-checkout @@ -776,6 +828,7 @@ complete -o default -o nospace -F _git_log git-whatchanged # included the '.exe' suffix. # if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then +complete -o default -F _git_apply git-apply.exe complete -o default -o nospace -F _git git.exe complete -o default -F _git_branch git-branch.exe complete -o default -o nospace -F _git_cat_file git-cat-file.exe -- cgit v0.10.2-6-g49f6 From 6f23ebf600188fe4246c2fb118f0c977ba1a2ed6 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 27 Nov 2006 13:20:53 -0800 Subject: git-svn: use ~/.subversion config files when using SVN:: libraries This allows users to use HTTP proxy information (among other settings) from ~/.subversion/servers and ~/.subversion/config --config-dir (as before) may be passed to git-svn to override the default choice of '~/.subversion' for the configuration directory. Thanks to tko on #git for pointing this out. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index de4e74a..d5d9c49 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2822,7 +2822,9 @@ sub libsvn_connect { SVN::Client::get_username_prompt_provider( \&_username_prompt, 2), ]); + my $config = SVN::Core::config_get_config($_config_dir); my $ra = SVN::Ra->new(url => $url, auth => $baton, + config => $config, pool => SVN::Pool->new, auth_provider_callbacks => $callbacks); $ra->{svn_path} = $url; @@ -2834,8 +2836,8 @@ sub libsvn_connect { sub libsvn_dup_ra { my ($ra) = @_; - SVN::Ra->new(map { $_ => $ra->{$_} } - qw/url auth auth_provider_callbacks repos_root svn_path/); + SVN::Ra->new(map { $_ => $ra->{$_} } qw/config url + auth auth_provider_callbacks repos_root svn_path/); } sub libsvn_get_file { -- cgit v0.10.2-6-g49f6 From 86d11cf264c55d570484cbdfff073092c77a6342 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 27 Nov 2006 14:21:30 -0800 Subject: cvsimport: style fixup. This should not change any functionality, but just makes it readable by having a space between syntactic construct keyword and open parenthesis (e.g. "if (expr", not "if(expr") and between close parenthesis and open brace (e.g. "if (expr) {" not "if (expr){"). Signed-off-by: Junio C Hamano diff --git a/git-cvsimport.perl b/git-cvsimport.perl index 4310dea..c5bf2d1 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -29,7 +29,7 @@ use IPC::Open2; $SIG{'PIPE'}="IGNORE"; $ENV{'TZ'}="UTC"; -our($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,$opt_M,$opt_A,$opt_S,$opt_L); +our ($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,$opt_M,$opt_A,$opt_S,$opt_L); my (%conv_author_name, %conv_author_email); sub usage() { @@ -90,15 +90,15 @@ usage if $opt_h; @ARGV <= 1 or usage(); -if($opt_d) { +if ($opt_d) { $ENV{"CVSROOT"} = $opt_d; -} elsif(-f 'CVS/Root') { +} elsif (-f 'CVS/Root') { open my $f, '<', 'CVS/Root' or die 'Failed to open CVS/Root'; $opt_d = <$f>; chomp $opt_d; close $f; $ENV{"CVSROOT"} = $opt_d; -} elsif($ENV{"CVSROOT"}) { +} elsif ($ENV{"CVSROOT"}) { $opt_d = $ENV{"CVSROOT"}; } else { die "CVSROOT needs to be set"; @@ -141,7 +141,7 @@ use File::Temp qw(tempfile); use POSIX qw(strftime dup2); sub new { - my($what,$repo,$subdir) = @_; + my ($what,$repo,$subdir) = @_; $what=ref($what) if ref($what); my $self = {}; @@ -161,38 +161,38 @@ sub new { sub conn { my $self = shift; my $repo = $self->{'fullrep'}; - if($repo =~ s/^:pserver(?:([^:]*)):(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?//) { - my($param,$user,$pass,$serv,$port) = ($1,$2,$3,$4,$5); + if ($repo =~ s/^:pserver(?:([^:]*)):(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?//) { + my ($param,$user,$pass,$serv,$port) = ($1,$2,$3,$4,$5); - my($proxyhost,$proxyport); - if($param && ($param =~ m/proxy=([^;]+)/)) { + my ($proxyhost,$proxyport); + if ($param && ($param =~ m/proxy=([^;]+)/)) { $proxyhost = $1; # Default proxyport, if not specified, is 8080. $proxyport = 8080; - if($ENV{"CVS_PROXY_PORT"}) { + if ($ENV{"CVS_PROXY_PORT"}) { $proxyport = $ENV{"CVS_PROXY_PORT"}; } - if($param =~ m/proxyport=([^;]+)/){ + if ($param =~ m/proxyport=([^;]+)/) { $proxyport = $1; } } $user="anonymous" unless defined $user; my $rr2 = "-"; - unless($port) { + unless ($port) { $rr2 = ":pserver:$user\@$serv:$repo"; $port=2401; } my $rr = ":pserver:$user\@$serv:$port$repo"; - unless($pass) { + unless ($pass) { open(H,$ENV{'HOME'}."/.cvspass") and do { # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah) { + while () { chomp; s/^\/\d+\s+//; my ($w,$p) = split(/\s/,$_,2); - if($w eq $rr or $w eq $rr2) { + if ($w eq $rr or $w eq $rr2) { $pass = $p; last; } @@ -202,7 +202,7 @@ sub conn { $pass="A" unless $pass; my ($s, $rep); - if($proxyhost) { + if ($proxyhost) { # Use a HTTP Proxy. Only works for HTTP proxies that # don't require user authentication @@ -218,7 +218,7 @@ sub conn { $rep = <$s>; # The answer should look like 'HTTP/1.x 2yy ....' - if(!($rep =~ m#^HTTP/1\.. 2[0-9][0-9]#)) { + if (!($rep =~ m#^HTTP/1\.. 2[0-9][0-9]#)) { die "Proxy connect: $rep\n"; } # Skip up to the empty line of the proxy server output @@ -239,7 +239,7 @@ sub conn { $rep = <$s>; - if($rep ne "I LOVE YOU\n") { + if ($rep ne "I LOVE YOU\n") { $rep="" unless $rep; die "AuthReply: $rep\n"; } @@ -271,7 +271,7 @@ sub conn { } } - unless($pid) { + unless ($pid) { $pr->writer(); $pw->reader(); dup2($pw->fileno(),0); @@ -294,7 +294,7 @@ sub conn { $self->{'socketo'}->flush(); chomp(my $rep=$self->readline()); - if($rep !~ s/^Valid-requests\s*//) { + if ($rep !~ s/^Valid-requests\s*//) { $rep="" unless $rep; die "Expected Valid-requests from server, but got: $rep\n"; } @@ -306,14 +306,14 @@ sub conn { } sub readline { - my($self) = @_; + my ($self) = @_; return $self->{'socketi'}->getline(); } sub _file { # Request a file with a given revision. # Trial and error says this is a good way to do it. :-/ - my($self,$fn,$rev) = @_; + my ($self,$fn,$rev) = @_; $self->{'socketo'}->write("Argument -N\n") or return undef; $self->{'socketo'}->write("Argument -P\n") or return undef; # -kk: Linus' version doesn't use it - defaults to off @@ -335,12 +335,12 @@ sub _file { sub _line { # Read a line from the server. # ... except that 'line' may be an entire file. ;-) - my($self, $fh) = @_; + my ($self, $fh) = @_; die "Not in lines" unless defined $self->{'lines'}; my $line; my $res=0; - while(defined($line = $self->readline())) { + while (defined($line = $self->readline())) { # M U gnupg-cvs-rep/AUTHORS # Updated gnupg-cvs-rep/ # /daten/src/rsync/gnupg-cvs-rep/AUTHORS @@ -349,7 +349,7 @@ sub _line { # 0 # ok - if($line =~ s/^(?:Created|Updated) //) { + if ($line =~ s/^(?:Created|Updated) //) { $line = $self->readline(); # path $line = $self->readline(); # Entries line my $mode = $self->readline(); chomp $mode; @@ -360,12 +360,12 @@ sub _line { die "Duh: Filesize $cnt" if $cnt !~ /^\d+$/; $line=""; $res = $self->_fetchfile($fh, $cnt); - } elsif($line =~ s/^ //) { + } elsif ($line =~ s/^ //) { print $fh $line; $res += length($line); - } elsif($line =~ /^M\b/) { + } elsif ($line =~ /^M\b/) { # output, do nothing - } elsif($line =~ /^Mbinary\b/) { + } elsif ($line =~ /^Mbinary\b/) { my $cnt; die "EOF from server after 'Mbinary'" unless defined ($cnt = $self->readline()); chomp $cnt; @@ -374,12 +374,12 @@ sub _line { $res += $self->_fetchfile($fh, $cnt); } else { chomp $line; - if($line eq "ok") { + if ($line eq "ok") { # print STDERR "S: ok (".length($res).")\n"; return $res; - } elsif($line =~ s/^E //) { + } elsif ($line =~ s/^E //) { # print STDERR "S: $line\n"; - } elsif($line =~ /^(Remove-entry|Removed) /i) { + } elsif ($line =~ /^(Remove-entry|Removed) /i) { $line = $self->readline(); # filename $line = $self->readline(); # OK chomp $line; @@ -393,7 +393,7 @@ sub _line { return undef; } sub file { - my($self,$fn,$rev) = @_; + my ($self,$fn,$rev) = @_; my $res; my ($fh, $name) = tempfile('gitcvs.XXXXXX', @@ -417,7 +417,7 @@ sub _fetchfile { my ($self, $fh, $cnt) = @_; my $res = 0; my $bufsize = 1024 * 1024; - while($cnt) { + while ($cnt) { if ($bufsize > $cnt) { $bufsize = $cnt; } @@ -438,7 +438,7 @@ my $cvs = CVSconn->new($opt_d, $cvs_tree); sub pdate($) { - my($d) = @_; + my ($d) = @_; m#(\d{2,4})/(\d\d)/(\d\d)\s(\d\d):(\d\d)(?::(\d\d))?# or die "Unparseable date: $d\n"; my $y=$1; $y-=1900 if $y>1900; @@ -446,22 +446,22 @@ sub pdate($) { } sub pmode($) { - my($mode) = @_; + my ($mode) = @_; my $m = 0; my $mm = 0; my $um = 0; for my $x(split(//,$mode)) { - if($x eq ",") { + if ($x eq ",") { $m |= $mm&$um; $mm = 0; $um = 0; - } elsif($x eq "u") { $um |= 0700; - } elsif($x eq "g") { $um |= 0070; - } elsif($x eq "o") { $um |= 0007; - } elsif($x eq "r") { $mm |= 0444; - } elsif($x eq "w") { $mm |= 0222; - } elsif($x eq "x") { $mm |= 0111; - } elsif($x eq "=") { # do nothing + } elsif ($x eq "u") { $um |= 0700; + } elsif ($x eq "g") { $um |= 0070; + } elsif ($x eq "o") { $um |= 0007; + } elsif ($x eq "r") { $mm |= 0444; + } elsif ($x eq "w") { $mm |= 0222; + } elsif ($x eq "x") { $mm |= 0111; + } elsif ($x eq "=") { # do nothing } else { die "Unknown mode: $mode\n"; } } @@ -485,7 +485,7 @@ sub get_headref ($$) { my $git_dir = shift; my $f = "$git_dir/refs/heads/$name"; - if(open(my $fh, $f)) { + if (open(my $fh, $f)) { chomp(my $r = <$fh>); is_sha1($r) or die "Cannot get head id for $name ($r): $!"; return $r; @@ -512,7 +512,7 @@ $orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE}; my %index; # holds filenames of one index per branch -unless(-d $git_dir) { +unless (-d $git_dir) { system("git-init-db"); die "Cannot init the GIT db at $git_tree: $?\n" if $?; system("git-read-tree"); @@ -531,7 +531,7 @@ unless(-d $git_dir) { chomp ($last_branch = ); $last_branch = basename($last_branch); close(F); - unless($last_branch) { + unless ($last_branch) { warn "Cannot read the last branch name: $! -- assuming 'master'\n"; $last_branch = "master"; } @@ -542,7 +542,7 @@ unless(-d $git_dir) { my $fmt = '($ref, $author) = (%(refname), %(author));'; open(H, "git-for-each-ref --perl --format='$fmt' refs/heads |") or die "Cannot run git-for-each-ref: $!\n"; - while(defined(my $entry = )) { + while (defined(my $entry = )) { my ($ref, $author); eval($entry) || die "cannot eval refs list: $@"; my ($head) = ($ref =~ m|^refs/heads/(.*)|); @@ -572,7 +572,7 @@ unless ($opt_P) { print "Running cvsps...\n" if $opt_v; my $pid = open(CVSPS,"-|"); die "Cannot fork: $!\n" unless defined $pid; - unless($pid) { + unless ($pid) { my @opt; @opt = split(/,/,$opt_p) if defined $opt_p; unshift @opt, '-z', $opt_z if defined $opt_z; @@ -642,8 +642,8 @@ sub write_tree () { return $tree; } -my($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg); -my(@old,@new,@skipped,%ignorebranch); +my ($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg); +my (@old,@new,@skipped,%ignorebranch); # commits that cvsps cannot place anywhere... $ignorebranch{'#CVSPS_NO_BRANCH'} = 1; @@ -684,7 +684,7 @@ sub commit { foreach my $rx (@mergerx) { next unless $logmsg =~ $rx && $1; my $mparent = $1 eq 'HEAD' ? $opt_o : $1; - if(my $sha1 = get_headref($mparent, $git_dir)) { + if (my $sha1 = get_headref($mparent, $git_dir)) { push @commit_args, '-p', $mparent; print "Merge parent branch: $mparent\n" if $opt_v; } @@ -725,9 +725,9 @@ sub commit { system("git-update-ref refs/heads/$branch $cid") == 0 or die "Cannot write branch $branch for update: $!\n"; - if($tag) { - my($in, $out) = ('',''); - my($xtag) = $tag; + if ($tag) { + my ($in, $out) = ('',''); + my ($xtag) = $tag; $xtag =~ s/\s+\*\*.*$//; # Remove stuff like ** INVALID ** and ** FUNKY ** $xtag =~ tr/_/\./ if ( $opt_u ); $xtag =~ s/[\/]/$opt_s/g; @@ -762,25 +762,25 @@ sub commit { }; my $commitcount = 1; -while() { +while () { chomp; - if($state == 0 and /^-+$/) { + if ($state == 0 and /^-+$/) { $state = 1; - } elsif($state == 0) { + } elsif ($state == 0) { $state = 1; redo; - } elsif(($state==0 or $state==1) and s/^PatchSet\s+//) { + } elsif (($state==0 or $state==1) and s/^PatchSet\s+//) { $patchset = 0+$_; $state=2; - } elsif($state == 2 and s/^Date:\s+//) { + } elsif ($state == 2 and s/^Date:\s+//) { $date = pdate($_); - unless($date) { + unless ($date) { print STDERR "Could not parse date: $_\n"; $state=0; next; } $state=3; - } elsif($state == 3 and s/^Author:\s+//) { + } elsif ($state == 3 and s/^Author:\s+//) { s/\s+$//; if (/^(.*?)\s+<(.*)>/) { ($author_name, $author_email) = ($1, $2); @@ -791,34 +791,34 @@ while() { $author_name = $author_email = $_; } $state = 4; - } elsif($state == 4 and s/^Branch:\s+//) { + } elsif ($state == 4 and s/^Branch:\s+//) { s/\s+$//; s/[\/]/$opt_s/g; $branch = $_; $state = 5; - } elsif($state == 5 and s/^Ancestor branch:\s+//) { + } elsif ($state == 5 and s/^Ancestor branch:\s+//) { s/\s+$//; $ancestor = $_; $ancestor = $opt_o if $ancestor eq "HEAD"; $state = 6; - } elsif($state == 5) { + } elsif ($state == 5) { $ancestor = undef; $state = 6; redo; - } elsif($state == 6 and s/^Tag:\s+//) { + } elsif ($state == 6 and s/^Tag:\s+//) { s/\s+$//; - if($_ eq "(none)") { + if ($_ eq "(none)") { $tag = undef; } else { $tag = $_; } $state = 7; - } elsif($state == 7 and /^Log:/) { + } elsif ($state == 7 and /^Log:/) { $logmsg = ""; $state = 8; - } elsif($state == 8 and /^Members:/) { + } elsif ($state == 8 and /^Members:/) { $branch = $opt_o if $branch eq "HEAD"; - if(defined $branch_date{$branch} and $branch_date{$branch} >= $date) { + if (defined $branch_date{$branch} and $branch_date{$branch} >= $date) { # skip print "skip patchset $patchset: $date before $branch_date{$branch}\n" if $opt_v; $state = 11; @@ -829,17 +829,17 @@ while() { $state = 11; next; } - if($ancestor) { - if($ancestor eq $branch) { + if ($ancestor) { + if ($ancestor eq $branch) { print STDERR "Branch $branch erroneously stems from itself -- changed ancestor to $opt_o\n"; $ancestor = $opt_o; } - if(-f "$git_dir/refs/heads/$branch") { + if (-f "$git_dir/refs/heads/$branch") { print STDERR "Branch $branch already exists!\n"; $state=11; next; } - unless(open(H,"$git_dir/refs/heads/$ancestor")) { + unless (open(H,"$git_dir/refs/heads/$ancestor")) { print STDERR "Branch $ancestor does not exist!\n"; $ignorebranch{$branch} = 1; $state=11; @@ -847,7 +847,7 @@ while() { } chomp(my $id = ); close(H); - unless(open(H,"> $git_dir/refs/heads/$branch")) { + unless (open(H,"> $git_dir/refs/heads/$branch")) { print STDERR "Could not create branch $branch: $!\n"; $ignorebranch{$branch} = 1; $state=11; @@ -860,9 +860,9 @@ while() { } $last_branch = $branch if $branch ne $last_branch; $state = 9; - } elsif($state == 8) { + } elsif ($state == 8) { $logmsg .= "$_\n"; - } elsif($state == 9 and /^\s+(.+?):(INITIAL|\d+(?:\.\d+)+)->(\d+(?:\.\d+)+)\s*$/) { + } elsif ($state == 9 and /^\s+(.+?):(INITIAL|\d+(?:\.\d+)+)->(\d+(?:\.\d+)+)\s*$/) { # VERSION:1.96->1.96.2.1 my $init = ($2 eq "INITIAL"); my $fn = $1; @@ -875,7 +875,7 @@ while() { } print "Fetching $fn v $rev\n" if $opt_v; my ($tmpname, $size) = $cvs->file($fn,$rev); - if($size == -1) { + if ($size == -1) { push(@old,$fn); print "Drop $fn\n" if $opt_v; } else { @@ -893,14 +893,14 @@ while() { push(@new,[$mode, $sha, $fn]); # may be resurrected! } unlink($tmpname); - } elsif($state == 9 and /^\s+(.+?):\d+(?:\.\d+)+->(\d+(?:\.\d+)+)\(DEAD\)\s*$/) { + } elsif ($state == 9 and /^\s+(.+?):\d+(?:\.\d+)+->(\d+(?:\.\d+)+)\(DEAD\)\s*$/) { my $fn = $1; $fn =~ s#^/+##; push(@old,$fn); print "Delete $fn\n" if $opt_v; - } elsif($state == 9 and /^\s*$/) { + } elsif ($state == 9 and /^\s*$/) { $state = 10; - } elsif(($state == 9 or $state == 10) and /^-+$/) { + } elsif (($state == 9 or $state == 10) and /^-+$/) { $commitcount++; if ($opt_L && $commitcount > $opt_L) { last; @@ -910,11 +910,11 @@ while() { system("git repack -a -d"); } $state = 1; - } elsif($state == 11 and /^-+$/) { + } elsif ($state == 11 and /^-+$/) { $state = 1; - } elsif(/^-+$/) { # end of unknown-line processing + } elsif (/^-+$/) { # end of unknown-line processing $state = 1; - } elsif($state != 11) { # ignore stuff when skipping + } elsif ($state != 11) { # ignore stuff when skipping print "* UNKNOWN LINE * $_\n"; } } @@ -943,7 +943,7 @@ if (defined $orig_git_index) { } # Now switch back to the branch we were in before all of this happened -if($orig_branch) { +if ($orig_branch) { print "DONE.\n" if $opt_v; if ($opt_i) { exit 0; -- cgit v0.10.2-6-g49f6 From 1d541c120b4de5c70e73f8a20e1d961324cc55fe Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 28 Nov 2006 00:29:21 +0100 Subject: shortlog: use pager On request of the kingpenguin, shortlog now uses the pager if output goes to a tty. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/git.c b/git.c index f97de60..357330e 100644 --- a/git.c +++ b/git.c @@ -260,7 +260,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "rev-parse", cmd_rev_parse, RUN_SETUP }, { "rm", cmd_rm, RUN_SETUP }, { "runstatus", cmd_runstatus, RUN_SETUP }, - { "shortlog", cmd_shortlog, RUN_SETUP }, + { "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER }, { "show-branch", cmd_show_branch, RUN_SETUP }, { "show", cmd_show, RUN_SETUP | USE_PAGER }, { "stripspace", cmd_stripspace }, -- cgit v0.10.2-6-g49f6 From f0df4ed5629c25e9978708115ad654b9f83f55df Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 28 Nov 2006 00:18:55 +0100 Subject: sha1_object_info(): be consistent with read_sha1_file() We used to try loose objects first with sha1_object_info(), but packed objects first with read_sha1_file(). Now, prefer packed objects over loose ones with sha1_object_info(), too. Usually the old behaviour would pose no problem, but when you tried to fix a fscked up repository by inserting a known-good pack, git cat-file $(git cat-file -t ) could fail, even when git cat-file blob would _not_ fail. Worse, a repack would fail, too. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/sha1_file.c b/sha1_file.c index 09456d2..63f416b 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1261,7 +1261,7 @@ struct packed_git *find_sha1_pack(const unsigned char *sha1, } -int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep) +static int sha1_loose_object_info(const unsigned char *sha1, char *type, unsigned long *sizep) { int status; unsigned long mapsize, size; @@ -1270,20 +1270,8 @@ int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep char hdr[128]; map = map_sha1_file(sha1, &mapsize); - if (!map) { - struct pack_entry e; - - if (!find_pack_entry(sha1, &e, NULL)) { - reprepare_packed_git(); - if (!find_pack_entry(sha1, &e, NULL)) - return error("unable to find %s", sha1_to_hex(sha1)); - } - if (use_packed_git(e.p)) - die("cannot map packed file"); - status = packed_object_info(e.p, e.offset, type, sizep); - unuse_packed_git(e.p); - return status; - } + if (!map) + return error("unable to find %s", sha1_to_hex(sha1)); if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0) status = error("unable to unpack %s header", sha1_to_hex(sha1)); @@ -1299,6 +1287,23 @@ int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep return status; } +int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep) +{ + int status; + struct pack_entry e; + + if (!find_pack_entry(sha1, &e, NULL)) { + reprepare_packed_git(); + if (!find_pack_entry(sha1, &e, NULL)) + return sha1_loose_object_info(sha1, type, sizep); + } + if (use_packed_git(e.p)) + die("cannot map packed file"); + status = packed_object_info(e.p, e.offset, type, sizep); + unuse_packed_git(e.p); + return status; +} + static void *read_packed_sha1(const unsigned char *sha1, char *type, unsigned long *size) { struct pack_entry e; -- cgit v0.10.2-6-g49f6 From 255cae88bcc8fcb0088459df13b55fa3d311b9e2 Mon Sep 17 00:00:00 2001 From: Andy Parkins Date: Sun, 26 Nov 2006 12:10:52 +0000 Subject: Use .git/config for storing "origin" shortcut repository Rather than use a separate config .git/remotes/ for remote shortcuts, this patch adds the analagous definitions to .git/config using git-repo-config calls. For example what was previously .git/remotes/origin URL: proto://host/path Pull: refs/heads/master:refs/heads/origin Is now added to .git/config as [remote "origin"] url = proto://host/path fetch = refs/heads/master:refs/heads/origin Signed-off-by: Andy Parkins Signed-off-by: Junio C Hamano diff --git a/git-clone.sh b/git-clone.sh index d4ee93f..b2d0f08 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -377,9 +377,9 @@ then *) origin_track="$remote_top/$origin" git-update-ref "refs/heads/$origin" "$head_sha1" ;; esac && - echo >"$GIT_DIR/remotes/$origin" \ - "URL: $repo -Pull: refs/heads/$head_points_at:$origin_track" && + git-repo-config remote."$origin".url "$repo" && + git-repo-config remote."$origin".fetch \ + "refs/heads/$head_points_at:$origin_track" && (cd "$GIT_DIR/$remote_top" && find . -type f -print) | while read dotslref do @@ -393,8 +393,8 @@ Pull: refs/heads/$head_points_at:$origin_track" && then continue fi - echo "Pull: refs/heads/${name}:$remote_top/${name}" - done >>"$GIT_DIR/remotes/$origin" && + git-repo-config remote."$origin".fetch "refs/heads/${name}:$remote_top/${name}" '^$' + done && case "$use_separate_remote" in t) rm -f "refs/remotes/$origin/HEAD" -- cgit v0.10.2-6-g49f6 From 27a1a8014b842c0d70fdc91c68dd361ca2dfb34c Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 27 Nov 2006 21:44:48 -0800 Subject: git-svn: enable delta transfers during fetches when using SVN:: libs This should drastically reduce bandwidth used for network transfers. This is not enabled for file:// repositories by default because of the increased CPU usage and I/O needed. GIT_SVN_DELTA_FETCH may be set to a true value to enable or false (0) to disable delta transfers regardless of the repository type. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index d5d9c49..9b86d91 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -68,7 +68,7 @@ my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit, $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit, $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m, $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive, - $_username, $_config_dir, $_no_auth_cache); + $_username, $_config_dir, $_no_auth_cache, $_xfer_delta); my (@_branch_from, %tree_map, %users, %rusers, %equiv); my ($_svn_co_url_revs, $_svn_pg_peg_revs); my @repo_path_split_cache; @@ -2675,6 +2675,9 @@ sub libsvn_load { require SVN::Ra; require SVN::Delta; push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor'; + push @SVN::Git::Fetcher::ISA, 'SVN::Delta::Editor'; + *SVN::Git::Fetcher::process_rm = *process_rm; + *SVN::Git::Fetcher::safe_qx = *safe_qx; my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file. $SVN::Node::dir.$SVN::Node::unknown. $SVN::Node::none.$SVN::Node::file. @@ -2827,6 +2830,13 @@ sub libsvn_connect { config => $config, pool => SVN::Pool->new, auth_provider_callbacks => $callbacks); + + my $df = $ENV{GIT_SVN_DELTA_FETCH}; + if (defined $df) { + $_xfer_delta = $df; + } else { + $_xfer_delta = ($url =~ m#^file://#) ? undef : 1; + } $ra->{svn_path} = $url; $ra->{repos_root} = $ra->get_repos_root; $ra->{svn_path} =~ s#^\Q$ra->{repos_root}\E/*##; @@ -2915,6 +2925,24 @@ sub process_rm { } sub libsvn_fetch { + $_xfer_delta ? libsvn_fetch_delta(@_) : libsvn_fetch_full(@_); +} + +sub libsvn_fetch_delta { + my ($last_commit, $paths, $rev, $author, $date, $msg) = @_; + my $pool = SVN::Pool->new; + my $ed = SVN::Git::Fetcher->new({ c => $last_commit, ra => $SVN, + paths => $paths }); + my $reporter = $SVN->do_update($rev, '', 1, $ed, $pool); + my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); + my (undef, $last_rev, undef) = cmt_metadata($last_commit); + $reporter->set_path('', $last_rev, 0, @lock, $pool); + $reporter->finish_report($pool); + $pool->clear; + libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]); +} + +sub libsvn_fetch_full { my ($last_commit, $paths, $rev, $author, $date, $msg) = @_; open my $gui, '| git-update-index -z --index-info' or croak $!; my @amr; @@ -3133,7 +3161,11 @@ sub libsvn_find_parent_branch { unlink $GIT_SVN_INDEX; print STDERR "Found branch parent: ($GIT_SVN) $parent\n"; sys(qw/git-read-tree/, $parent); - return libsvn_fetch($parent, $paths, $rev, + # I can't seem to get do_switch() to work correctly with + # the SWIG interface (TypeError when passing switch_url...), + # so we'll unconditionally bypass the delta interface here + # for now + return libsvn_fetch_full($parent, $paths, $rev, $author, $date, $msg); } print STDERR "Nope, branch point not imported or unknown\n"; @@ -3153,9 +3185,19 @@ sub libsvn_new_tree { return $log_entry; } my ($paths, $rev, $author, $date, $msg) = @_; - open my $gui, '| git-update-index -z --index-info' or croak $!; - libsvn_traverse($gui, '', $SVN->{svn_path}, $rev); - close $gui or croak $?; + if ($_xfer_delta) { + my $pool = SVN::Pool->new; + my $ed = SVN::Git::Fetcher->new({paths => $paths, ra => $SVN}); + my $reporter = $SVN->do_update($rev, '', 1, $ed, $pool); + my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); + $reporter->set_path('', $rev, 1, @lock, $pool); + $reporter->finish_report($pool); + $pool->clear; + } else { + open my $gui, '| git-update-index -z --index-info' or croak $!; + libsvn_traverse($gui, '', $SVN->{svn_path}, $rev); + close $gui or croak $?; + } return libsvn_log_entry($rev, $author, $date, $msg); } @@ -3325,6 +3367,148 @@ sub copy_remote_ref { "refs/remotes/$GIT_SVN on $origin\n"; } } +package SVN::Git::Fetcher; +use vars qw/@ISA/; +use strict; +use warnings; +use Carp qw/croak/; +use IO::File qw//; + +# file baton members: path, mode_a, mode_b, pool, fh, blob, base +sub new { + my ($class, $git_svn) = @_; + my $self = SVN::Delta::Editor->new; + bless $self, $class; + open my $gui, '| git-update-index -z --index-info' or croak $!; + $self->{gui} = $gui; + $self->{c} = $git_svn->{c} if exists $git_svn->{c}; + if (my $p = $git_svn->{paths} && $git_svn->{ra}) { + my $s = $git_svn->{ra}->{svn_path}; + $s = length $s ? qr#^/\Q$s\E/# : qr#^/#; + $self->{paths} = { map { my $x = $_; + $x =~ s/$s//; + $x => $p->{$_} } keys %$p }; + } + require Digest::MD5; + $self; +} + +sub delete_entry { + my ($self, $path, $rev, $pb) = @_; + process_rm($self->{gui}, $self->{c}, $path); + undef; +} + +sub open_file { + my ($self, $path, $pb, $rev) = @_; + my ($mode, $blob) = (safe_qx('git-ls-tree',$self->{c},'--',$path) + =~ /^(\d{6}) blob ([a-f\d]{40})\t/); + { path => $path, mode_a => $mode, mode_b => $mode, blob => $blob, + pool => SVN::Pool->new }; +} + +sub add_file { + my ($self, $path, $pb, $cp_path, $cp_rev) = @_; + { path => $path, mode_a => 100644, mode_b => 100644, + pool => SVN::Pool->new }; +} + +sub change_file_prop { + my ($self, $fb, $prop, $value) = @_; + if ($prop eq 'svn:executable') { + if ($fb->{mode_b} != 120000) { + $fb->{mode_b} = defined $value ? 100755 : 100644; + } + } elsif ($prop eq 'svn:special') { + $fb->{mode_b} = defined $value ? 120000 : 100644; + } + undef; +} + +sub apply_textdelta { + my ($self, $fb, $exp) = @_; + my $fh = IO::File->new_tmpfile; + $fh->autoflush(1); + # $fh gets auto-closed() by SVN::TxDelta::apply(), + # (but $base does not,) so dup() it for reading in close_file + open my $dup, '<&', $fh or croak $!; + my $base = IO::File->new_tmpfile; + $base->autoflush(1); + if ($fb->{blob}) { + defined (my $pid = fork) or croak $!; + if (!$pid) { + open STDOUT, '>&', $base or croak $!; + print STDOUT 'link ' if ($fb->{mode_a} == 120000); + exec qw/git-cat-file blob/, $fb->{blob} or croak $!; + } + waitpid $pid, 0; + croak $? if $?; + + if (defined $exp) { + seek $base, 0, 0 or croak $!; + my $md5 = Digest::MD5->new; + $md5->addfile($base); + my $got = $md5->hexdigest; + die "Checksum mismatch: $fb->{path} $fb->{blob}\n", + "expected: $exp\n", + " got: $got\n" if ($got ne $exp); + } + } + seek $base, 0, 0 or croak $!; + $fb->{fh} = $dup; + $fb->{base} = $base; + [ SVN::TxDelta::apply($base, $fh, undef, $fb->{path}, $fb->{pool}) ]; +} + +sub close_file { + my ($self, $fb, $exp) = @_; + my $hash; + my $path = $fb->{path}; + if (my $fh = $fb->{fh}) { + seek($fh, 0, 0) or croak $!; + my $md5 = Digest::MD5->new; + $md5->addfile($fh); + my $got = $md5->hexdigest; + die "Checksum mismatch: $path\n", + "expected: $exp\n got: $got\n" if ($got ne $exp); + seek($fh, 0, 0) or croak $!; + if ($fb->{mode_b} == 120000) { + read($fh, my $buf, 5) == 5 or croak $!; + $buf eq 'link ' or die "$path has mode 120000", + "but is not a link\n"; + } + defined(my $pid = open my $out,'-|') or die "Can't fork: $!\n"; + if (!$pid) { + open STDIN, '<&', $fh or croak $!; + exec qw/git-hash-object -w --stdin/ or croak $!; + } + chomp($hash = do { local $/; <$out> }); + close $out or croak $!; + close $fh or croak $!; + $hash =~ /^[a-f\d]{40}$/ or die "not a sha1: $hash\n"; + close $fb->{base} or croak $!; + } else { + $hash = $fb->{blob} or die "no blob information\n"; + } + $fb->{pool}->clear; + my $gui = $self->{gui}; + print $gui "$fb->{mode_b} $hash\t$path\0" or croak $!; + print "\t", $self->{paths}->{$path}->action, + "\t$path\n" if defined $self->{paths}->{$path}; + undef; +} + +sub abort_edit { + my $self = shift; + close $self->{gui}; + $self->SUPER::abort_edit(@_); +} + +sub close_edit { + my $self = shift; + close $self->{gui} or croak; + $self->SUPER::close_edit(@_); +} package SVN::Git::Editor; use vars qw/@ISA/; -- cgit v0.10.2-6-g49f6 From e15161198a381334b8c8ccee707392163f3a1ab7 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 27 Nov 2006 21:46:50 -0800 Subject: git-svn: update tests for recent changes * Enable test for delta transfers in full-svn-test. * Run tests against the root of the repository so we won't have to revisit 308906fa6e98132cab839a4f42701386fba368ef and efe4631def181d32f932672a7ea31e52ee0ab308 again. The graft-branches test still runs as before. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/t/Makefile b/t/Makefile index e1c6c92..c9bd9a4 100644 --- a/t/Makefile +++ b/t/Makefile @@ -23,8 +23,9 @@ clean: # we can test NO_OPTIMIZE_COMMITS independently of LC_ALL full-svn-test: + $(MAKE) $(TSVN) GIT_SVN_NO_LIB=0 GIT_SVN_DELTA_FETCH=1 \ + GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C $(MAKE) $(TSVN) GIT_SVN_NO_LIB=1 GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C - $(MAKE) $(TSVN) GIT_SVN_NO_LIB=0 GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C $(MAKE) $(TSVN) GIT_SVN_NO_LIB=1 GIT_SVN_NO_OPTIMIZE_COMMITS=0 \ LC_ALL=en_US.UTF-8 $(MAKE) $(TSVN) GIT_SVN_NO_LIB=0 GIT_SVN_NO_OPTIMIZE_COMMITS=0 \ diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index 29a1e72..63c6703 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh @@ -45,6 +45,6 @@ else svnadmin create "$svnrepo" fi -svnrepo="file://$svnrepo/test-git-svn" +svnrepo="file://$svnrepo" diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index 34a3ccd..f9de232 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -228,6 +228,11 @@ tree 56a30b966619b863674f5978696f4a3594f2fca9 tree d667270a1f7b109f5eb3aaea21ede14b56bfdd6e tree 8f51f74cf0163afc9ad68a4b1537288c4558b5a4 EOF + +if test -z "$GIT_SVN_NO_LIB" || test "$GIT_SVN_NO_LIB" -eq 0; then + echo tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 >> expected +fi + test_expect_success "$name" "diff -u a expected" test_done diff --git a/t/t9103-git-svn-graft-branches.sh b/t/t9103-git-svn-graft-branches.sh index cc62d4e..293b98f 100755 --- a/t/t9103-git-svn-graft-branches.sh +++ b/t/t9103-git-svn-graft-branches.sh @@ -1,6 +1,8 @@ test_description='git-svn graft-branches' . ./lib-git-svn.sh +svnrepo="$svnrepo/test-git-svn" + test_expect_success 'initialize repo' " mkdir import && cd import && -- cgit v0.10.2-6-g49f6 From c3e43938839752b48ff3a36862ae59f1cd1e630d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Tue, 28 Nov 2006 22:49:17 +0100 Subject: shortlog: remove range check Don't force the user to specify more than one revision parameter, thus making git-shortlog behave more like git-log. 'git-shortlog master' will now produce the expected results; the other end of the range simply is the (oldest) root commit. Signed-off-by: Rene Scharfe Signed-off-by: Junio C Hamano diff --git a/builtin-shortlog.c b/builtin-shortlog.c index b5b13de..f1124e2 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -298,9 +298,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) if (!access(".mailmap", R_OK)) read_mailmap(".mailmap"); - if (rev.pending.nr == 1) - die ("Need a range!"); - else if (rev.pending.nr == 0) + if (rev.pending.nr == 0) read_from_stdin(&list); else get_from_rev(&rev, &list); -- cgit v0.10.2-6-g49f6 From dad73c0bb9f33323ec1aacf560a6263f1d85f81a Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 28 Nov 2006 14:06:05 -0800 Subject: git-svn: error out when the SVN connection fails during a fetch finish_report does seem to return a useful value indicating success or failure, so we'll just set a flag when close_edit is called (it is not called on failures, nor is abort_edit) and check the flag before proceeding. Thanks to Pazu for pointing this out. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index 9b86d91..cefa7b0 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2939,6 +2939,9 @@ sub libsvn_fetch_delta { $reporter->set_path('', $last_rev, 0, @lock, $pool); $reporter->finish_report($pool); $pool->clear; + unless ($ed->{git_commit_ok}) { + die "SVN connection failed somewhere...\n"; + } libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]); } @@ -3193,6 +3196,9 @@ sub libsvn_new_tree { $reporter->set_path('', $rev, 1, @lock, $pool); $reporter->finish_report($pool); $pool->clear; + unless ($ed->{git_commit_ok}) { + die "SVN connection failed somewhere...\n"; + } } else { open my $gui, '| git-update-index -z --index-info' or croak $!; libsvn_traverse($gui, '', $SVN->{svn_path}, $rev); @@ -3506,7 +3512,8 @@ sub abort_edit { sub close_edit { my $self = shift; - close $self->{gui} or croak; + close $self->{gui} or croak $!; + $self->{git_commit_ok} = 1; $self->SUPER::close_edit(@_); } -- cgit v0.10.2-6-g49f6 From 0864e3ba12ed946f0082297530584a77e2a7097b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 28 Nov 2006 02:50:17 -0800 Subject: git-svn: fix output reporting from the delta fetcher There was nothing printed in the code originally because I left out a pair of parentheses. Nevertheless, the affected code has been replaced with a more efficient version that respects the -q flag as well as requiring less bandwidth. We save some bandwidth by not requesting changed paths information when calling get_log() since we're using the delta fetcher. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index cefa7b0..c3ad5ec 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1152,7 +1152,7 @@ sub graft_file_copy_lib { while (1) { my $pool = SVN::Pool->new; libsvn_get_log(libsvn_dup_ra($SVN), [$path], - $min, $max, 0, 1, 1, + $min, $max, 0, 2, 1, sub { libsvn_graft_file_copies($grafts, $tree_paths, $path, @_); @@ -2906,7 +2906,7 @@ sub libsvn_log_entry { } sub process_rm { - my ($gui, $last_commit, $f) = @_; + my ($gui, $last_commit, $f, $q) = @_; # remove entire directories. if (safe_qx('git-ls-tree',$last_commit,'--',$f) =~ /^040000 tree/) { defined(my $pid = open my $ls, '-|') or croak $!; @@ -2917,10 +2917,13 @@ sub process_rm { local $/ = "\0"; while (<$ls>) { print $gui '0 ',0 x 40,"\t",$_ or croak $!; + print "\tD\t$_\n" unless $q; } + print "\tD\t$f/\n" unless $q; close $ls or croak $?; } else { print $gui '0 ',0 x 40,"\t",$f,"\0" or croak $!; + print "\tD\t$f\n" unless $q; } } @@ -2931,8 +2934,7 @@ sub libsvn_fetch { sub libsvn_fetch_delta { my ($last_commit, $paths, $rev, $author, $date, $msg) = @_; my $pool = SVN::Pool->new; - my $ed = SVN::Git::Fetcher->new({ c => $last_commit, ra => $SVN, - paths => $paths }); + my $ed = SVN::Git::Fetcher->new({ c => $last_commit, q => $_q }); my $reporter = $SVN->do_update($rev, '', 1, $ed, $pool); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); my (undef, $last_rev, undef) = cmt_metadata($last_commit); @@ -2959,8 +2961,7 @@ sub libsvn_fetch_full { $f =~ s#^/##; } if ($m =~ /^[DR]$/) { - print "\t$m\t$f\n" unless $_q; - process_rm($gui, $last_commit, $f); + process_rm($gui, $last_commit, $f, $_q); next if $m eq 'D'; # 'R' can be file replacements, too, right? } @@ -3101,7 +3102,7 @@ sub revisions_eq { # should be OK to use Pool here (r1 - r0) should be small my $pool = SVN::Pool->new; libsvn_get_log($SVN, [$path], $r0, $r1, - 0, 1, 1, sub {$nr++}, $pool); + 0, 0, 1, sub {$nr++}, $pool); $pool->clear; } else { my ($url, undef) = repo_path_split($SVN_URL); @@ -3177,6 +3178,7 @@ sub libsvn_find_parent_branch { sub libsvn_get_log { my ($ra, @args) = @_; + $args[4]-- if $args[4] && $_xfer_delta && ! $_follow_parent; if ($SVN::Core::VERSION le '1.2.0') { splice(@args, 3, 1); } @@ -3190,7 +3192,7 @@ sub libsvn_new_tree { my ($paths, $rev, $author, $date, $msg) = @_; if ($_xfer_delta) { my $pool = SVN::Pool->new; - my $ed = SVN::Git::Fetcher->new({paths => $paths, ra => $SVN}); + my $ed = SVN::Git::Fetcher->new({q => $_q}); my $reporter = $SVN->do_update($rev, '', 1, $ed, $pool); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); $reporter->set_path('', $rev, 1, @lock, $pool); @@ -3388,20 +3390,14 @@ sub new { open my $gui, '| git-update-index -z --index-info' or croak $!; $self->{gui} = $gui; $self->{c} = $git_svn->{c} if exists $git_svn->{c}; - if (my $p = $git_svn->{paths} && $git_svn->{ra}) { - my $s = $git_svn->{ra}->{svn_path}; - $s = length $s ? qr#^/\Q$s\E/# : qr#^/#; - $self->{paths} = { map { my $x = $_; - $x =~ s/$s//; - $x => $p->{$_} } keys %$p }; - } + $self->{q} = $git_svn->{q}; require Digest::MD5; $self; } sub delete_entry { my ($self, $path, $rev, $pb) = @_; - process_rm($self->{gui}, $self->{c}, $path); + process_rm($self->{gui}, $self->{c}, $path, $self->{q}); undef; } @@ -3410,13 +3406,13 @@ sub open_file { my ($mode, $blob) = (safe_qx('git-ls-tree',$self->{c},'--',$path) =~ /^(\d{6}) blob ([a-f\d]{40})\t/); { path => $path, mode_a => $mode, mode_b => $mode, blob => $blob, - pool => SVN::Pool->new }; + pool => SVN::Pool->new, action => 'M' }; } sub add_file { my ($self, $path, $pb, $cp_path, $cp_rev) = @_; { path => $path, mode_a => 100644, mode_b => 100644, - pool => SVN::Pool->new }; + pool => SVN::Pool->new, action => 'A' }; } sub change_file_prop { @@ -3499,8 +3495,7 @@ sub close_file { $fb->{pool}->clear; my $gui = $self->{gui}; print $gui "$fb->{mode_b} $hash\t$path\0" or croak $!; - print "\t", $self->{paths}->{$path}->action, - "\t$path\n" if defined $self->{paths}->{$path}; + print "\t$fb->{action}\t$path\n" if $fb->{action} && ! $self->{q}; undef; } -- cgit v0.10.2-6-g49f6 From 4548e855e4600a0f76329cf4f0dd9e8d17d66b08 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 28 Nov 2006 12:12:08 -0500 Subject: Teach bash how to complete long options for git-commit. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index d8ae4d7..be978cf 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -398,6 +398,20 @@ _git_cherry_pick () esac } +_git_commit () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + --*) + COMPREPLY=($(compgen -W " + --all --author= --signoff --verify --no-verify + --edit --amend --include --only + " -- "$cur")) + return + esac + COMPREPLY=() +} + _git_diff () { __git_complete_file @@ -768,6 +782,7 @@ _git () cat-file) _git_cat_file ;; checkout) _git_checkout ;; cherry-pick) _git_cherry_pick ;; + commit) _git_commit ;; diff) _git_diff ;; diff-tree) _git_diff_tree ;; fetch) _git_fetch ;; @@ -804,6 +819,7 @@ complete -o default -F _git_branch git-branch complete -o default -o nospace -F _git_cat_file git-cat-file complete -o default -F _git_checkout git-checkout complete -o default -F _git_cherry_pick git-cherry-pick +complete -o default -F _git_commit git-commit complete -o default -o nospace -F _git_diff git-diff complete -o default -F _git_diff_tree git-diff-tree complete -o default -o nospace -F _git_fetch git-fetch -- cgit v0.10.2-6-g49f6 From 67ffa1142585742601011440a17528ef841bbf44 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 28 Nov 2006 12:12:26 -0500 Subject: Fix broken bash completion of local refs. Commit 35e65ecc broke completion of local refs, e.g. "git pull . fo" no longer would complete to "foo". Instead it printed out an internal git error ("fatal: Not a git repository: '.'"). The break occurred when I tried to improve performance by switching from git-peek-remote to git-for-each-ref. Apparently git-peek-remote will drop into directory "$1/.git" (where $1 is its first parameter) if it is given a repository with a working directory. This allowed the bash completion code to work properly even though it was not handing over the true repository directory. So now we do a stat in bash to see if we need to add "/.git" to the path string before running any command with --git-dir. I also tried to optimize away two "git rev-parse --git-dir" invocations in common cases like "git log fo" as typically the user is in the top level directory of their project and therefore the .git subdirectory is in the current working directory. This should make a difference on systems where fork+exec might take a little while. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index be978cf..447ec20 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -34,7 +34,19 @@ __gitdir () { - echo "${__git_dir:-$(git rev-parse --git-dir 2>/dev/null)}" + if [ -z "$1" ]; then + if [ -n "$__git_dir" ]; then + echo "$__git_dir" + elif [ -d .git ]; then + echo .git + else + git rev-parse --git-dir 2>/dev/null + fi + elif [ -d "$1/.git" ]; then + echo "$1/.git" + else + echo "$1" + fi } __git_ps1 () @@ -51,7 +63,7 @@ __git_ps1 () __git_heads () { - local cmd i is_hash=y dir="${1:-$(__gitdir)}" + local cmd i is_hash=y dir="$(__gitdir "$1")" if [ -d "$dir" ]; then for i in $(git --git-dir="$dir" \ for-each-ref --format='%(refname)' \ @@ -60,7 +72,7 @@ __git_heads () done return fi - for i in $(git-ls-remote "$dir" 2>/dev/null); do + for i in $(git-ls-remote "$1" 2>/dev/null); do case "$is_hash,$i" in y,*) is_hash=n ;; n,*^{}) is_hash=y ;; @@ -72,7 +84,7 @@ __git_heads () __git_refs () { - local cmd i is_hash=y dir="${1:-$(__gitdir)}" + local cmd i is_hash=y dir="$(__gitdir "$1")" if [ -d "$dir" ]; then if [ -e "$dir/HEAD" ]; then echo HEAD; fi for i in $(git --git-dir="$dir" \ @@ -101,20 +113,9 @@ __git_refs () __git_refs2 () { - local cmd i is_hash=y dir="${1:-$(__gitdir)}" - if [ -d "$dir" ]; then - cmd=git-peek-remote - else - cmd=git-ls-remote - fi - for i in $($cmd "$dir" 2>/dev/null); do - case "$is_hash,$i" in - y,*) is_hash=n ;; - n,*^{}) is_hash=y ;; - n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}:${i#refs/tags/}" ;; - n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}:${i#refs/heads/}" ;; - n,*) is_hash=y; echo "$i:$i" ;; - esac + local i + for i in $(__git_refs "$1"); do + echo "$i:$i" done } -- cgit v0.10.2-6-g49f6 From d5cc2de9ff57d2f6734f6e6e34addc2fae1b5b0e Mon Sep 17 00:00:00 2001 From: Han-Wen Nienhuys Date: Tue, 28 Nov 2006 11:27:39 +0100 Subject: ident.c: Trim hint printed when gecos is empty. Also remove asterisks for readability, and suggest use of git-config for easy cut & pasting. Signed-off-by: Han-Wen Nienhuys Signed-off-by: Junio C Hamano diff --git a/ident.c b/ident.c index efec97f..e415fd3 100644 --- a/ident.c +++ b/ident.c @@ -158,12 +158,17 @@ static int copy(char *buf, int size, int offset, const char *src) static const char au_env[] = "GIT_AUTHOR_NAME"; static const char co_env[] = "GIT_COMMITTER_NAME"; static const char *env_hint = -"\n*** Environment problem:\n" +"\n" "*** Your name cannot be determined from your system services (gecos).\n" -"*** You would need to set %s and %s\n" -"*** environment variables; otherwise you won't be able to perform\n" -"*** certain operations because of \"empty ident\" errors.\n" -"*** Alternatively, you can use user.name configuration variable.\n\n"; +"\n" +"Run\n" +"\n" +" git repo-config user.email \"you@email.com\"\n" +" git repo-config user.name \"Your Name\"\n" +"\n" +"To set the identity in this repository.\n" +"Add --global to set your account\'s default\n" +"\n"; static const char *get_ident(const char *name, const char *email, const char *date_str, int error_on_no_name) -- cgit v0.10.2-6-g49f6 From 9aca025849d417b04b49fead126c915f6ca47526 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 28 Nov 2006 18:51:40 -0800 Subject: git-svn: color support for the log command * match LESS environment settings to those in pager.c * parse diff.color and pager.color settings in the config file, and pass --color to git-log * --color and --pager= settings are supported Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index c3ad5ec..d8d8716 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -60,6 +60,7 @@ nag_lib() unless $_use_lib; my $_optimize_commits = 1 unless $ENV{GIT_SVN_NO_OPTIMIZE_COMMITS}; my $sha1 = qr/[a-f\d]{40}/; my $sha1_short = qr/[a-f\d]{4,40}/; +my $_esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/; my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit, $_find_copies_harder, $_l, $_cp_similarity, $_cp_remote, $_repack, $_repack_nr, $_repack_flags, $_q, @@ -68,7 +69,8 @@ my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit, $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit, $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m, $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive, - $_username, $_config_dir, $_no_auth_cache, $_xfer_delta); + $_username, $_config_dir, $_no_auth_cache, $_xfer_delta, + $_pager, $_color); my (@_branch_from, %tree_map, %users, %rusers, %equiv); my ($_svn_co_url_revs, $_svn_pg_peg_revs); my @repo_path_split_cache; @@ -135,6 +137,8 @@ my %cmd = ( 'show-commit' => \$_show_commit, 'non-recursive' => \$_non_recursive, 'authors-file|A=s' => \$_authors, + 'color' => \$_color, + 'pager=s' => \$_pager, } ], 'commit-diff' => [ \&commit_diff, 'Commit a diff between two trees', { 'message|m=s' => \$_message, @@ -759,16 +763,17 @@ sub show_log { } } + config_pager(); my $pid = open(my $log,'-|'); defined $pid or croak $!; if (!$pid) { exec(git_svn_log_cmd($r_min,$r_max), @args) or croak $!; } - setup_pager(); + run_pager(); my (@k, $c, $d); while (<$log>) { - if (/^commit ($sha1_short)/o) { + if (/^${_esc_color}commit ($sha1_short)/o) { my $cmt = $1; if ($c && cmt_showable($c) && $c->{r} != $r_last) { $r_last = $c->{r}; @@ -777,25 +782,25 @@ sub show_log { } $d = undef; $c = { c => $cmt }; - } elsif (/^author (.+) (\d+) ([\-\+]?\d+)$/) { + } elsif (/^${_esc_color}author (.+) (\d+) ([\-\+]?\d+)$/) { get_author_info($c, $1, $2, $3); - } elsif (/^(?:tree|parent|committer) /) { + } elsif (/^${_esc_color}(?:tree|parent|committer) /) { # ignore - } elsif (/^:\d{6} \d{6} $sha1_short/o) { + } elsif (/^${_esc_color}:\d{6} \d{6} $sha1_short/o) { push @{$c->{raw}}, $_; - } elsif (/^[ACRMDT]\t/) { + } elsif (/^${_esc_color}[ACRMDT]\t/) { # we could add $SVN->{svn_path} here, but that requires # remote access at the moment (repo_path_split)... - s#^([ACRMDT])\t# $1 #; + s#^(${_esc_color})([ACRMDT])\t#$1 $2 #; push @{$c->{changed}}, $_; - } elsif (/^diff /) { + } elsif (/^${_esc_color}diff /) { $d = 1; push @{$c->{diff}}, $_; } elsif ($d) { push @{$c->{diff}}, $_; - } elsif (/^ (git-svn-id:.+)$/) { + } elsif (/^${_esc_color} (git-svn-id:.+)$/) { ($c->{url}, $c->{r}, undef) = extract_metadata($1); - } elsif (s/^ //) { + } elsif (s/^${_esc_color} //) { push @{$c->{l}}, $_; } } @@ -901,12 +906,30 @@ sub cmt_showable { return defined $c->{r}; } +sub log_use_color { + return 1 if $_color; + my $dc; + chomp($dc = `git-repo-config --get diff.color`); + if ($dc eq 'auto') { + if (-t *STDOUT || (defined $_pager && + `git-repo-config --bool --get pager.color` !~ /^false/)) { + return ($ENV{TERM} && $ENV{TERM} ne 'dumb'); + } + return 0; + } + return 0 if $dc eq 'never'; + return 1 if $dc eq 'always'; + chomp($dc = `git-repo-config --bool --get diff.color`); + $dc eq 'true'; +} + sub git_svn_log_cmd { my ($r_min, $r_max) = @_; my @cmd = (qw/git-log --abbrev-commit --pretty=raw --default/, "refs/remotes/$GIT_SVN"); push @cmd, '-r' unless $_non_recursive; push @cmd, qw/--raw --name-status/ if $_verbose; + push @cmd, '--color' if log_use_color(); return @cmd unless defined $r_max; if ($r_max == $r_min) { push @cmd, '--max-count=1'; @@ -2533,14 +2556,18 @@ sub tz_to_s_offset { return ($1 * 60) + ($tz * 3600); } -sub setup_pager { # translated to Perl from pager.c - return unless (-t *STDOUT); - my $pager = $ENV{PAGER}; - if (!defined $pager) { - $pager = 'less'; - } elsif (length $pager == 0 || $pager eq 'cat') { - return; +# adapted from pager.c +sub config_pager { + $_pager ||= $ENV{GIT_PAGER} || $ENV{PAGER}; + if (!defined $_pager) { + $_pager = 'less'; + } elsif (length $_pager == 0 || $_pager eq 'cat') { + $_pager = undef; } +} + +sub run_pager { + return unless -t *STDOUT; pipe my $rfd, my $wfd or return; defined(my $pid = fork) or croak $!; if (!$pid) { @@ -2548,8 +2575,8 @@ sub setup_pager { # translated to Perl from pager.c return; } open STDIN, '<&', $rfd or croak $!; - $ENV{LESS} ||= '-S'; - exec $pager or croak "Can't run pager: $!\n";; + $ENV{LESS} ||= 'FRSX'; + exec $_pager or croak "Can't run pager: $! ($_pager)\n"; } sub get_author_info { -- cgit v0.10.2-6-g49f6 From 4511c899e64cbda934ba864c359a2a7a04909264 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 28 Nov 2006 18:51:41 -0800 Subject: git-svn: documentation updates Eliminate 'commit' from some places and plug 'dcommit' more. Also update the section --id (GIT_SVN_ID) usage since we have multi-init/multi-fetch now. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index a764d1f..a45067e 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -49,7 +49,7 @@ latest revision. Note: You should never attempt to modify the remotes/git-svn branch outside of git-svn. Instead, create a branch from -remotes/git-svn and work on that branch. Use the 'commit' +remotes/git-svn and work on that branch. Use the 'dcommit' command (see below) to write git commits back to remotes/git-svn. @@ -274,7 +274,7 @@ ADVANCED OPTIONS -b:: --branch :: -Used with 'fetch' or 'commit'. +Used with 'fetch', 'dcommit' or 'commit'. This can be used to join arbitrary git branches to remotes/git-svn on new commits where the tree object is equivalent. @@ -368,7 +368,7 @@ SVN was very wrong. Basic Examples ~~~~~~~~~~~~~~ -Tracking and contributing to an Subversion managed-project: +Tracking and contributing to a Subversion-managed project: ------------------------------------------------------------------------ # Initialize a repo (like git init-db): @@ -377,10 +377,9 @@ Tracking and contributing to an Subversion managed-project: git-svn fetch # Create your own branch to hack on: git checkout -b my-branch remotes/git-svn -# Commit only the git commits you want to SVN: - git-svn commit [ ...] -# Commit all the git commits from my-branch that don't exist in SVN: - git-svn commit remotes/git-svn..my-branch +# Do some work, and then commit your new changes to SVN, as well as +# automatically updating your working HEAD: + git-svn dcommit # Something is committed to SVN, rebase the latest into your branch: git-svn fetch && git rebase remotes/git-svn # Append svn:ignore settings to the default git exclude file: @@ -404,26 +403,24 @@ which can lead to merge commits reversing previous commits in SVN. DESIGN PHILOSOPHY ----------------- Merge tracking in Subversion is lacking and doing branched development -with Subversion is cumbersome as a result. git-svn completely forgoes -any automated merge/branch tracking on the Subversion side and leaves it -entirely up to the user on the git side. It's simply not worth it to do -a useful translation when the original signal is weak. +with Subversion is cumbersome as a result. git-svn does not do +automated merge/branch tracking by default and leaves it entirely up to +the user on the git side. [[tracking-multiple-repos]] TRACKING MULTIPLE REPOSITORIES OR BRANCHES ------------------------------------------ -This is for advanced users, most users should ignore this section. - Because git-svn does not care about relationships between different branches or directories in a Subversion repository, git-svn has a simple hack to allow it to track an arbitrary number of related _or_ unrelated -SVN repositories via one git repository. Simply set the GIT_SVN_ID -environment variable to a name other other than "git-svn" (the default) -and git-svn will ignore the contents of the $GIT_DIR/svn/git-svn directory -and instead do all of its work in $GIT_DIR/svn/$GIT_SVN_ID for that -invocation. The interface branch will be remotes/$GIT_SVN_ID, instead of -remotes/git-svn. Any remotes/$GIT_SVN_ID branch should never be modified -by the user outside of git-svn commands. +SVN repositories via one git repository. Simply use the --id/-i flag or +set the GIT_SVN_ID environment variable to a name other other than +"git-svn" (the default) and git-svn will ignore the contents of the +$GIT_DIR/svn/git-svn directory and instead do all of its work in +$GIT_DIR/svn/$GIT_SVN_ID for that invocation. The interface branch will +be remotes/$GIT_SVN_ID, instead of remotes/git-svn. Any +remotes/$GIT_SVN_ID branch should never be modified by the user outside +of git-svn commands. [[fetch-args]] ADDITIONAL FETCH ARGUMENTS @@ -486,7 +483,8 @@ If you are not using the SVN::* Perl libraries and somebody commits a conflicting changeset to SVN at a bad moment (right before you commit) causing a conflict and your commit to fail, your svn working tree ($GIT_DIR/git-svn/tree) may be dirtied. The easiest thing to do is -probably just to rm -rf $GIT_DIR/git-svn/tree and run 'rebuild'. +probably just to rm -rf $GIT_DIR/git-svn/tree and run 'rebuild'. You +can avoid this problem entirely by using 'dcommit'. We ignore all SVN properties except svn:executable. Too difficult to map them since we rely heavily on git write-tree being _exactly_ the -- cgit v0.10.2-6-g49f6 From 1ca7558dd838e82f6f6b8611b981654fa4ecde2b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 28 Nov 2006 18:51:42 -0800 Subject: git-svn: fix multi-init After the bugfix to connect to repositories where the user has limited read permissions, multi-init was broken due to our SVN::Ra connection being limited to working in a subdirectory; so we now create a new Ra connection for init-ing branches and another for tags Along with that fix, allow the user to use the command-line option flags for multi-init (--revision being the most notable; but also --no-auth-cache, --config-dir, --username (for passing to SVN), and --shared/--template for passing to git-init-db Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index d8d8716..3891122 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -124,7 +124,12 @@ my %cmd = ( 'no-graft-copy' => \$_no_graft_copy } ], 'multi-init' => [ \&multi_init, 'Initialize multiple trees (like git-svnimport)', - { %multi_opts, %fc_opts } ], + { %multi_opts, %init_opts, + 'revision|r=i' => \$_revision, + 'username=s' => \$_username, + 'config-dir=s' => \$_config_dir, + 'no-auth-cache' => \$_no_auth_cache, + } ], 'multi-fetch' => [ \&multi_fetch, 'Fetch multiple trees (like git-svnimport)', \%fc_opts ], @@ -3316,11 +3321,11 @@ sub libsvn_commit_cb { sub libsvn_ls_fullurl { my $fullurl = shift; - $SVN ||= libsvn_connect($fullurl); + my $ra = libsvn_connect($fullurl); my @ret; my $pool = SVN::Pool->new; - my ($dirent, undef, undef) = $SVN->get_dir($SVN->{svn_path}, - $SVN->get_latest_revnum, $pool); + my $r = defined $_revision ? $_revision : $ra->get_latest_revnum; + my ($dirent, undef, undef) = $ra->get_dir('', $r, $pool); foreach my $d (keys %$dirent) { if ($dirent->{$d}->kind == $SVN::Node::dir) { push @ret, "$d/"; # add '/' for compat with cli svn -- cgit v0.10.2-6-g49f6 From ab3bb800b46cf66249ead9e13c22cbc48d790c9a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 28 Nov 2006 22:29:18 -0800 Subject: git blame -C: fix output format tweaks when crossing file boundary. We used to get the case that more than two paths came from the same commit wrong when computing the output width and deciding to turn on --show-name option automatically. When we find that lines that came from a path that is different from what we started digging from, we should always turn --show-name on, and we should count the name length for all files involved. Signed-off-by: Junio C Hamano diff --git a/builtin-blame.c b/builtin-blame.c index 066dee7..53fed45 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -1435,14 +1435,14 @@ static void find_alignment(struct scoreboard *sb, int *option) struct commit_info ci; int num; + if (strcmp(suspect->path, sb->path)) + *option |= OUTPUT_SHOW_NAME; + num = strlen(suspect->path); + if (longest_file < num) + longest_file = num; if (!(suspect->commit->object.flags & METAINFO_SHOWN)) { suspect->commit->object.flags |= METAINFO_SHOWN; get_commit_info(suspect->commit, &ci, 1); - if (strcmp(suspect->path, sb->path)) - *option |= OUTPUT_SHOW_NAME; - num = strlen(suspect->path); - if (longest_file < num) - longest_file = num; num = strlen(ci.author); if (longest_author < num) longest_author = num; -- cgit v0.10.2-6-g49f6 From 6bee4e408c097a4f0c4a091f13dacabe7c766025 Mon Sep 17 00:00:00 2001 From: Alex Riesen Date: Wed, 15 Nov 2006 22:52:25 +0100 Subject: git-blame: fix rev parameter handling. We lacked "--" termination in the underlying init_revisions() call which made it impossible to specify a revision that happens to have the same name as an existing file. Signed-off-by: Junio C Hamano diff --git a/builtin-blame.c b/builtin-blame.c index 53fed45..dc3ffea 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -1787,6 +1787,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) /* Now we got rev and path. We do not want the path pruning * but we may want "bottom" processing. */ + argv[unk++] = "--"; /* terminate the rev name */ argv[unk] = NULL; init_revisions(&revs, NULL); -- cgit v0.10.2-6-g49f6 From 665892307013bccacb35dd619ae6951c7b209379 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 29 Nov 2006 00:17:01 -0800 Subject: tutorial: talk about user.name early and don't start with commit -a Introducing yourself to git early would be a good idea; otherwise the user may not find the mistake until much later when "git log" is learned. Teaching "commit -a" without saying that it is a shortcut for listing the paths to commit leaves the user puzzled. Teach the form with explicit paths first. Signed-off-by: Junio C Hamano diff --git a/Documentation/tutorial.txt b/Documentation/tutorial.txt index 1e4ddfb..6555e58 100644 --- a/Documentation/tutorial.txt +++ b/Documentation/tutorial.txt @@ -11,6 +11,18 @@ diff" with: $ man git-diff ------------------------------------------------ +It is a good idea to introduce yourself to git before doing any +operation. The easiest way to do so is: + +------------------------------------------------ +$ cat >~/.gitconfig <<\EOF +[user] + name = Your Name Comes Here + email = you@yourdomain.example.com +EOF +------------------------------------------------ + + Importing a new project ----------------------- @@ -31,7 +43,8 @@ defaulting to local storage area You've now initialized the working directory--you may notice a new directory created, named ".git". Tell git that you want it to track -every file under the current directory with +every file under the current directory with (notice the dot '.' +that means the current directory): ------------------------------------------------ $ git add . @@ -40,7 +53,7 @@ $ git add . Finally, ------------------------------------------------ -$ git commit -a +$ git commit ------------------------------------------------ will prompt you for a commit message, then record the current state @@ -55,11 +68,17 @@ $ git diff to review your changes. When you're done, ------------------------------------------------ -$ git commit -a +$ git commit file1 file2... ------------------------------------------------ will again prompt your for a message describing the change, and then -record the new versions of the modified files. +record the new versions of the files you listed. It is cumbersome +to list all files and you can say `-a` (which stands for 'all') +instead. + +------------------------------------------------ +$ git commit -a +------------------------------------------------ A note on commit messages: Though not required, it's a good idea to begin the commit message with a single short (less than 50 character) @@ -75,7 +94,7 @@ $ git add path/to/new/file ------------------------------------------------ then commit as usual. No special command is required when removing a -file; just remove it, then commit. +file; just remove it, then tell `commit` about the file as usual. At any point you can view the history of your changes using -- cgit v0.10.2-6-g49f6 From eb07fd59acae0f043b9fac2a8a1cb427036c6f71 Mon Sep 17 00:00:00 2001 From: Andy Parkins Date: Wed, 29 Nov 2006 08:25:40 +0000 Subject: Document git-repo-config --bool/--int options. Signed-off-by: Andy Parkins Signed-off-by: Junio C Hamano diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt index 8199615..5bede9a 100644 --- a/Documentation/git-repo-config.txt +++ b/Documentation/git-repo-config.txt @@ -77,6 +77,12 @@ OPTIONS -l, --list:: List all variables set in config file. +--bool:: + git-repo-config will ensure that the output is "true" or "false" + +--int:: + git-repo-config will ensure that the output is a simple decimal number + ENVIRONMENT ----------- -- cgit v0.10.2-6-g49f6 From 67c08ce14fb488562666ab896541ad75f1bdcca6 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 29 Nov 2006 17:15:48 -0500 Subject: pack-objects: remove redundent status information The final 'nr_result' and 'written' values must always be the same otherwise we're in deep trouble. So let's remove a redundent report. And for paranoia sake let's make sure those two variables are actually equal after all objects are written (one never knows). Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 753bcd5..a2dc7d1 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -514,6 +514,8 @@ static void write_pack_file(void) if (do_progress) fputc('\n', stderr); done: + if (written != nr_result) + die("wrote %d objects while expecting %d", written, nr_result); sha1close(f, pack_file_sha1, 1); } @@ -1662,7 +1664,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) } } if (progress) - fprintf(stderr, "Total %d, written %d (delta %d), reused %d (delta %d)\n", - nr_result, written, written_delta, reused, reused_delta); + fprintf(stderr, "Total %d (delta %d), reused %d (delta %d)\n", + written, written_delta, reused, reused_delta); return 0; } -- cgit v0.10.2-6-g49f6 From ced7b828fadbf3d6de49d75392f1516b4ceb4491 Mon Sep 17 00:00:00 2001 From: Andreas Ericsson Date: Thu, 30 Nov 2006 12:28:28 +0100 Subject: ls-files: Give hints when errors happen. Without this patch "git commit file.c file2.c" produces the not so stellar output: error: pathspec 'file.c' did not match any. error: pathspec 'file2.c' did not match any. With this patch, the output is changed to: error: pathspec 'file.c' did not match any file(s) known to git. error: pathspec 'file2.c' did not match any file(s) known to git. Did you forget to 'git add'? Signed-off-by: Andreas Ericsson Signed-off-by: Junio C Hamano diff --git a/builtin-ls-files.c b/builtin-ls-files.c index ad8c41e..bc79ce4 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -487,10 +487,14 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) for (num = 0; pathspec[num]; num++) { if (ps_matched[num]) continue; - error("pathspec '%s' did not match any.", + error("pathspec '%s' did not match any file(s) known to git.", pathspec[num] + prefix_offset); errors++; } + + if (errors) + fprintf(stderr, "Did you forget to 'git add'?\n"); + return errors ? 1 : 0; } -- cgit v0.10.2-6-g49f6 From 4c81c213a479e4aae0653a56ad6e8db5c31f019c Mon Sep 17 00:00:00 2001 From: Andreas Ericsson Date: Thu, 30 Nov 2006 12:43:13 +0100 Subject: git-diff: Introduce --index and deprecate --cached. 'git diff --cached' still works, but its use is discouraged in the documentation. 'git diff --index' does the same thing and is consistent with how 'git apply --index' works. Signed-off-by: Andreas Ericsson Signed-off-by: Junio C Hamano diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt index 228c4d9..3144864 100644 --- a/Documentation/git-diff.txt +++ b/Documentation/git-diff.txt @@ -22,8 +22,10 @@ the number of trees given to the command. * When one is given, the working tree and the named tree are compared, using `git-diff-index`. The option - `--cached` can be given to compare the index file and + `--index` can be given to compare the index file and the named tree. + `--cached` is a deprecated alias for `--index`. It's use is + discouraged. * When two s are given, these two trees are compared using `git-diff-tree`. @@ -47,7 +49,7 @@ Various ways to check your working tree:: + ------------ $ git diff <1> -$ git diff --cached <2> +$ git diff --index <2> $ git diff HEAD <3> ------------ + diff --git a/builtin-diff.c b/builtin-diff.c index a659020..1c535b1 100644 --- a/builtin-diff.c +++ b/builtin-diff.c @@ -137,7 +137,7 @@ static int builtin_diff_index(struct rev_info *revs, int cached = 0; while (1 < argc) { const char *arg = argv[1]; - if (!strcmp(arg, "--cached")) + if (!strcmp(arg, "--index") || !strcmp(arg, "--cached")) cached = 1; else usage(builtin_diff_usage); -- cgit v0.10.2-6-g49f6 From 22b1c7ee01ef5bc7f81e620bb88a6fad79c1c605 Mon Sep 17 00:00:00 2001 From: Andy Parkins Date: Thu, 30 Nov 2006 10:50:28 +0000 Subject: De-emphasise the symbolic link documentation. The fact that git has previously used symbolic links for representing symbolic refs doesn't seem relevant to the current function of git-symbolic-ref. This patch makes less of a big deal about the symbolic link history and instead focuses on what git does now. Signed-off-by: Andy Parkins Signed-off-by: Junio C Hamano diff --git a/Documentation/git-symbolic-ref.txt b/Documentation/git-symbolic-ref.txt index 68ac6a6..4bc35a1 100644 --- a/Documentation/git-symbolic-ref.txt +++ b/Documentation/git-symbolic-ref.txt @@ -19,29 +19,22 @@ argument to see on which branch your working tree is on. Give two arguments, create or update a symbolic ref to point at the given branch . -Traditionally, `.git/HEAD` is a symlink pointing at -`refs/heads/master`. When we want to switch to another branch, -we did `ln -sf refs/heads/newbranch .git/HEAD`, and when we want +A symbolic ref is a regular file that stores a string that +begins with `ref: refs/`. For example, your `.git/HEAD` is +a regular file whose contents is `ref: refs/heads/master`. + +NOTES +----- +In the past, `.git/HEAD` was a symbolic link pointing at +`refs/heads/master`. When we wanted to switch to another branch, +we did `ln -sf refs/heads/newbranch .git/HEAD`, and when we wanted to find out which branch we are on, we did `readlink .git/HEAD`. This was fine, and internally that is what still happens by default, but on platforms that do not have working symlinks, or that do not have the `readlink(1)` command, this was a bit cumbersome. On some platforms, `ln -sf` does not even work as -advertised (horrors). - -A symbolic ref can be a regular file that stores a string that -begins with `ref: refs/`. For example, your `.git/HEAD` *can* -be a regular file whose contents is `ref: refs/heads/master`. -This can be used on a filesystem that does not support symbolic -links. Instead of doing `readlink .git/HEAD`, `git-symbolic-ref -HEAD` can be used to find out which branch we are on. To point -the HEAD to `newbranch`, instead of `ln -sf refs/heads/newbranch -.git/HEAD`, `git-symbolic-ref HEAD refs/heads/newbranch` can be -used. - -Currently, .git/HEAD uses a regular file symbolic ref on Cygwin, -and everywhere else it is implemented as a symlink. This can be -changed at compilation time. +advertised (horrors). Therefore symbolic links are now deprecated +and symbolic refs are used by default. Author ------ -- cgit v0.10.2-6-g49f6 From 3683dc5a9afaf88d00e55c9e6c67a2160ca7fc9c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 2 Dec 2006 16:58:30 -0800 Subject: git-merge: fix confusion between tag and branch In a repository with core.warnambiguousrefs turned off, and with a branch and a tag that have the same name 'frotz', git merge frotz would merge the commit pointed at by the tag 'frotz' but incorrectly would identify what was merged as 'branch frotz' in the merge message. Signed-off-by: Junio C Hamano diff --git a/git-merge.sh b/git-merge.sh index 75af10d..272f004 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -189,13 +189,13 @@ else merge_name=$(for remote do rh=$(git-rev-parse --verify "$remote"^0 2>/dev/null) && - if git show-ref -q --verify "refs/heads/$remote" + bh=$(git show-ref -s --verify "refs/heads/$remote") && + if test "$rh" = "$bh" then - what=branch + echo "$rh branch '$remote' of ." else - what=commit - fi && - echo "$rh $what '$remote'" + echo "$rh commit '$remote'" + fi done | git-fmt-merge-msg ) merge_msg="${merge_msg:+$merge_msg$LF$LF}$merge_name" -- cgit v0.10.2-6-g49f6 From 6173c197c9a23fa8594f18fd2c856407d4af31c1 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 2 Dec 2006 16:19:31 -0800 Subject: git-svn: avoid fetching files twice in the same revision SVN is not entirely consistent in returning log information and sometimes returns file information when adding subdirectories, and sometimes it does not (only returning information about the directory that was added). This caused git-svn to occasionally add a file to the list of files to be fetched twice. Now we change the data structure to be hash to avoid repeated fetches. As of now (in master), this only affects repositories fetched without deltas enabled (file://, and when manually overriden with GIT_SVN_DELTA_FETCH=0); so this bug mainly affects users of 1.4.4.1 and maint. Thanks to Florian Weimer for reporting this bug. [jc: backported for maint] Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index bb8935a..b53273e 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2778,7 +2778,7 @@ sub process_rm { sub libsvn_fetch { my ($last_commit, $paths, $rev, $author, $date, $msg) = @_; open my $gui, '| git-update-index -z --index-info' or croak $!; - my @amr; + my %amr; foreach my $f (keys %$paths) { my $m = $paths->{$f}->action(); $f =~ s#^/+##; @@ -2792,7 +2792,7 @@ sub libsvn_fetch { my $t = $SVN->check_path($f, $rev, $pool); if ($t == $SVN::Node::file) { if ($m =~ /^[AMR]$/) { - push @amr, [ $m, $f ]; + $amr{$f} = $m; } else { die "Unrecognized action: $m, ($f r$rev)\n"; } @@ -2800,13 +2800,13 @@ sub libsvn_fetch { my @traversed = (); libsvn_traverse($gui, '', $f, $rev, \@traversed); foreach (@traversed) { - push @amr, [ $m, $_ ] + $amr{$_} = $m; } } $pool->clear; } - foreach (@amr) { - libsvn_get_file($gui, $_->[1], $rev, $_->[0]); + foreach (keys %amr) { + libsvn_get_file($gui, $_, $rev, $amr{$_}); } close $gui or croak $?; return libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]); -- cgit v0.10.2-6-g49f6 From 857b933e04bc21ce02043c3107c148f8dcbb4a01 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 21 Nov 2006 23:24:34 +0100 Subject: xdiff: add xdl_merge() This new function implements the functionality of RCS merge, but in-memory. It returns < 0 on error, otherwise the number of conflicts. Finding the conflicting lines can be a very expensive task. You can control the eagerness of this algorithm: - a level value of 0 means that all overlapping changes are treated as conflicts, - a value of 1 means that if the overlapping changes are identical, it is not treated as a conflict. - If you set level to 2, overlapping changes will be analyzed, so that almost identical changes will not result in huge conflicts. Rather, only the conflicting lines will be shown inside conflict markers. With each increasing level, the algorithm gets slower, but more accurate. Note that the code for level 2 depends on the simple definition of mmfile_t specific to git, and therefore it will be harder to port that to LibXDiff. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index de3e9f3..23bbb90 100644 --- a/Makefile +++ b/Makefile @@ -723,7 +723,8 @@ $(DIFF_OBJS): diffcore.h $(LIB_FILE): $(LIB_OBJS) rm -f $@ && $(AR) rcs $@ $(LIB_OBJS) -XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o +XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \ + xdiff/xmerge.o $(XDIFF_OBJS): xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \ xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h index c9f8178..fa409d5 100644 --- a/xdiff/xdiff.h +++ b/xdiff/xdiff.h @@ -49,6 +49,9 @@ extern "C" { #define XDL_BDOP_CPY 2 #define XDL_BDOP_INSB 3 +#define XDL_MERGE_MINIMAL 0 +#define XDL_MERGE_EAGER 1 +#define XDL_MERGE_ZEALOUS 2 typedef struct s_mmfile { char *ptr; @@ -90,6 +93,10 @@ long xdl_mmfile_size(mmfile_t *mmf); int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t const *xecfg, xdemitcb_t *ecb); +int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, + mmfile_t *mf2, const char *name2, + xpparam_t const *xpp, int level, mmbuffer_t *result); + #ifdef __cplusplus } #endif /* #ifdef __cplusplus */ diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c index d76e76a..9aeebc4 100644 --- a/xdiff/xdiffi.c +++ b/xdiff/xdiffi.c @@ -45,7 +45,6 @@ static long xdl_split(unsigned long const *ha1, long off1, long lim1, long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl, xdalgoenv_t *xenv); static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, long chg2); -static int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags); @@ -397,7 +396,7 @@ static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, } -static int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { +int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { long ix, ixo, ixs, ixref, grpsiz, nrec = xdf->nrec; char *rchg = xdf->rchg, *rchgo = xdfo->rchg; xrecord_t **recs = xdf->recs; diff --git a/xdiff/xdiffi.h b/xdiff/xdiffi.h index d3b7271..472aeae 100644 --- a/xdiff/xdiffi.h +++ b/xdiff/xdiffi.h @@ -50,6 +50,7 @@ int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1, long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv); int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdfenv_t *xe); +int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags); int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr); void xdl_free_script(xdchange_t *xscr); int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c new file mode 100644 index 0000000..7b85aa5 --- /dev/null +++ b/xdiff/xmerge.c @@ -0,0 +1,433 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003-2006 Davide Libenzi, Johannes E. Schindelin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi + * + */ + +#include "xinclude.h" + +typedef struct s_xdmerge { + struct s_xdmerge *next; + /* + * 0 = conflict, + * 1 = no conflict, take first, + * 2 = no conflict, take second. + */ + int mode; + long i1, i2; + long chg1, chg2; +} xdmerge_t; + +static int xdl_append_merge(xdmerge_t **merge, int mode, + long i1, long chg1, long i2, long chg2) +{ + xdmerge_t *m = *merge; + if (m && mode == m->mode && + (i1 == m->i1 + m->chg1 || i2 == m->i2 + m->chg2)) { + m->chg1 = i1 + chg1 - m->i1; + m->chg2 = i2 + chg2 - m->i2; + } else { + m = xdl_malloc(sizeof(xdmerge_t)); + if (!m) + return -1; + m->next = NULL; + m->mode = mode; + m->i1 = i1; + m->chg1 = chg1; + m->i2 = i2; + m->chg2 = chg2; + if (*merge) + (*merge)->next = m; + *merge = m; + } + return 0; +} + +static int xdl_cleanup_merge(xdmerge_t *c) +{ + int count = 0; + xdmerge_t *next_c; + + /* were there conflicts? */ + for (; c; c = next_c) { + if (c->mode == 0) + count++; + next_c = c->next; + free(c); + } + return count; +} + +static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2, + int line_count, long flags) +{ + int i; + xrecord_t **rec1 = xe1->xdf2.recs + i1; + xrecord_t **rec2 = xe2->xdf2.recs + i2; + + for (i = 0; i < line_count; i++) { + int result = xdl_recmatch(rec1[i]->ptr, rec1[i]->size, + rec2[i]->ptr, rec2[i]->size, flags); + if (!result) + return -1; + } + return 0; +} + +static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest) +{ + xrecord_t **recs = xe->xdf2.recs + i; + int size = 0; + + if (count < 1) + return 0; + + for (i = 0; i < count; size += recs[i++]->size) + if (dest) + memcpy(dest + size, recs[i]->ptr, recs[i]->size); + if (add_nl) { + i = recs[count - 1]->size; + if (i == 0 || recs[count - 1]->ptr[i - 1] != '\n') { + if (dest) + dest[size] = '\n'; + size++; + } + } + return size; +} + +static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, + xdfenv_t *xe2, const char *name2, xdmerge_t *m, char *dest) +{ + const int marker_size = 7; + int marker1_size = (name1 ? strlen(name1) + 1 : 0); + int marker2_size = (name2 ? strlen(name2) + 1 : 0); + int conflict_marker_size = 3 * (marker_size + 1) + + marker1_size + marker2_size; + int size, i1, j; + + for (size = i1 = 0; m; m = m->next) { + if (m->mode == 0) { + size += xdl_recs_copy(xe1, i1, m->i1 - i1, 0, + dest ? dest + size : NULL); + if (dest) { + for (j = 0; j < marker_size; j++) + dest[size++] = '<'; + if (marker1_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name1, + marker1_size - 1); + size += marker1_size; + } + dest[size++] = '\n'; + } else + size += conflict_marker_size; + size += xdl_recs_copy(xe1, m->i1, m->chg1, 1, + dest ? dest + size : NULL); + if (dest) { + for (j = 0; j < marker_size; j++) + dest[size++] = '='; + dest[size++] = '\n'; + } + size += xdl_recs_copy(xe2, m->i2, m->chg2, 1, + dest ? dest + size : NULL); + if (dest) { + for (j = 0; j < marker_size; j++) + dest[size++] = '>'; + if (marker2_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name2, + marker2_size - 1); + size += marker2_size; + } + dest[size++] = '\n'; + } + } else if (m->mode == 1) + size += xdl_recs_copy(xe1, i1, m->i1 + m->chg1 - i1, 0, + dest ? dest + size : NULL); + else if (m->mode == 2) + size += xdl_recs_copy(xe2, m->i2 - m->i1 + i1, + m->i1 + m->chg2 - i1, 0, + dest ? dest + size : NULL); + i1 = m->i1 + m->chg1; + } + size += xdl_recs_copy(xe1, i1, xe1->xdf2.nrec - i1, 0, + dest ? dest + size : NULL); + return size; +} + +/* + * Sometimes, changes are not quite identical, but differ in only a few + * lines. Try hard to show only these few lines as conflicting. + */ +static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m, + xpparam_t const *xpp) +{ + for (; m; m = m->next) { + mmfile_t t1, t2; + xdfenv_t xe; + xdchange_t *xscr, *x; + int i1 = m->i1, i2 = m->i2; + + /* let's handle just the conflicts */ + if (m->mode) + continue; + + /* + * This probably does not work outside git, since + * we have a very simple mmfile structure. + */ + t1.ptr = (char *)xe1->xdf2.recs[m->i1]->ptr; + t1.size = xe1->xdf2.recs[m->i1 + m->chg1]->ptr + + xe1->xdf2.recs[m->i1 + m->chg1]->size - t1.ptr; + t2.ptr = (char *)xe2->xdf2.recs[m->i1]->ptr; + t2.size = xe2->xdf2.recs[m->i1 + m->chg1]->ptr + + xe2->xdf2.recs[m->i1 + m->chg1]->size - t2.ptr; + if (xdl_do_diff(&t1, &t2, xpp, &xe) < 0) + return -1; + if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe, &xscr) < 0) { + xdl_free_env(&xe); + return -1; + } + if (!xscr) { + /* If this happens, it's a bug. */ + xdl_free_env(&xe); + return -2; + } + x = xscr; + m->i1 = xscr->i1 + i1; + m->chg1 = xscr->chg1; + m->i2 = xscr->i2 + i2; + m->chg2 = xscr->chg2; + while (xscr->next) { + xdmerge_t *m2 = xdl_malloc(sizeof(xdmerge_t)); + if (!m2) { + xdl_free_env(&xe); + xdl_free_script(x); + return -1; + } + xscr = xscr->next; + m2->next = m->next; + m->next = m2; + m = m2; + m->mode = 0; + m->i1 = xscr->i1 + i1; + m->chg1 = xscr->chg1; + m->i2 = xscr->i2 + i2; + m->chg2 = xscr->chg2; + } + xdl_free_env(&xe); + xdl_free_script(x); + } + return 0; +} + +/* + * level == 0: mark all overlapping changes as conflict + * level == 1: mark overlapping changes as conflict only if not identical + * level == 2: analyze non-identical changes for minimal conflict set + * + * returns < 0 on error, == 0 for no conflicts, else number of conflicts + */ +static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, + xdfenv_t *xe2, xdchange_t *xscr2, const char *name2, + int level, xpparam_t const *xpp, mmbuffer_t *result) { + xdmerge_t *changes, *c; + int i1, i2, chg1, chg2; + + c = changes = NULL; + + while (xscr1 && xscr2) { + if (!changes) + changes = c; + if (xscr1->i1 + xscr1->chg1 < xscr2->i1) { + i1 = xscr1->i2; + i2 = xscr2->i2 - xscr2->i1 + xscr1->i1; + chg1 = xscr1->chg2; + chg2 = xscr1->chg1; + if (xdl_append_merge(&c, 1, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr1 = xscr1->next; + continue; + } + if (xscr2->i1 + xscr2->chg1 < xscr1->i1) { + i1 = xscr1->i2 - xscr1->i1 + xscr2->i1; + i2 = xscr2->i2; + chg1 = xscr2->chg1; + chg2 = xscr2->chg2; + if (xdl_append_merge(&c, 2, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr2 = xscr2->next; + continue; + } + if (level < 1 || xscr1->i1 != xscr2->i1 || + xscr1->chg1 != xscr2->chg1 || + xscr1->chg2 != xscr2->chg2 || + xdl_merge_cmp_lines(xe1, xscr1->i2, + xe2, xscr2->i2, + xscr1->chg2, xpp->flags)) { + /* conflict */ + int off = xscr1->i1 - xscr2->i1; + int ffo = off + xscr1->chg1 - xscr2->chg1; + + i1 = xscr1->i2; + i2 = xscr2->i2; + if (off > 0) + i1 -= off; + else + i2 += off; + chg1 = xscr1->i2 + xscr1->chg2 - i1; + chg2 = xscr2->i2 + xscr2->chg2 - i2; + if (ffo > 0) + chg2 += ffo; + else + chg1 -= ffo; + if (xdl_append_merge(&c, 0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + } + + i1 = xscr1->i1 + xscr1->chg1; + i2 = xscr2->i1 + xscr2->chg1; + + if (i1 > i2) { + xscr1->chg1 -= i1 - i2; + xscr1->i1 = i2; + xscr1->i2 += xscr1->chg2; + xscr1->chg2 = 0; + xscr1 = xscr1->next; + } else if (i2 > i1) { + xscr2->chg1 -= i2 - i1; + xscr2->i1 = i1; + xscr2->i2 += xscr2->chg2; + xscr2->chg2 = 0; + xscr2 = xscr2->next; + } else { + xscr1 = xscr1->next; + xscr2 = xscr2->next; + } + } + while (xscr1) { + if (!changes) + changes = c; + i1 = xscr1->i2; + i2 = xscr1->i1 + xe2->xdf2.nrec - xe2->xdf1.nrec; + chg1 = xscr1->chg2; + chg2 = xscr1->chg1; + if (xdl_append_merge(&c, 1, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr1 = xscr1->next; + } + while (xscr2) { + if (!changes) + changes = c; + i1 = xscr2->i1 + xe1->xdf2.nrec - xe1->xdf1.nrec; + i2 = xscr2->i2; + chg1 = xscr2->chg1; + chg2 = xscr2->chg2; + if (xdl_append_merge(&c, 2, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr2 = xscr2->next; + } + if (!changes) + changes = c; + /* refine conflicts */ + if (level > 1 && xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0) { + xdl_cleanup_merge(changes); + return -1; + } + /* output */ + if (result) { + int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2, + changes, NULL); + result->ptr = xdl_malloc(size); + if (!result->ptr) { + xdl_cleanup_merge(changes); + return -1; + } + result->size = size; + xdl_fill_merge_buffer(xe1, name1, xe2, name2, changes, + result->ptr); + } + return xdl_cleanup_merge(changes); +} + +int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, + mmfile_t *mf2, const char *name2, + xpparam_t const *xpp, int level, mmbuffer_t *result) { + xdchange_t *xscr1, *xscr2; + xdfenv_t xe1, xe2; + + result->ptr = NULL; + result->size = 0; + + if (xdl_do_diff(orig, mf1, xpp, &xe1) < 0 || + xdl_do_diff(orig, mf2, xpp, &xe2) < 0) { + return -1; + } + if (xdl_change_compact(&xe1.xdf1, &xe1.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe1.xdf2, &xe1.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe1, &xscr1) < 0) { + xdl_free_env(&xe1); + return -1; + } + if (xdl_change_compact(&xe2.xdf1, &xe2.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe2.xdf2, &xe2.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe2, &xscr2) < 0) { + xdl_free_env(&xe2); + return -1; + } + if (xscr1 || xscr2) { + if (!xscr1) { + result->ptr = xdl_malloc(mf2->size); + memcpy(result->ptr, mf2->ptr, mf2->size); + result->size = mf2->size; + } else if (!xscr2) { + result->ptr = xdl_malloc(mf1->size); + memcpy(result->ptr, mf1->ptr, mf1->size); + result->size = mf1->size; + } else if (xdl_do_merge(&xe1, xscr1, name1, + &xe2, xscr2, name2, + level, xpp, result) < 0) { + xdl_free_script(xscr1); + xdl_free_script(xscr2); + xdl_free_env(&xe1); + xdl_free_env(&xe2); + return -1; + } + xdl_free_script(xscr1); + xdl_free_script(xscr2); + } + xdl_free_env(&xe1); + xdl_free_env(&xe2); + + return 0; +} + -- cgit v0.10.2-6-g49f6 From 53a7085a104936aef29400369aab15e0d1ccbb28 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 29 Nov 2006 12:06:49 -0800 Subject: xmerge: make return value from xdl_merge() more usable. The callers would want to know if the resulting merge is clean; do not discard that information away after calling xdl_do_merge(). Signed-off-by: Junio C Hamano diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c index 7b85aa5..286f177 100644 --- a/xdiff/xmerge.c +++ b/xdiff/xmerge.c @@ -384,6 +384,7 @@ int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, xpparam_t const *xpp, int level, mmbuffer_t *result) { xdchange_t *xscr1, *xscr2; xdfenv_t xe1, xe2; + int status; result->ptr = NULL; result->size = 0; @@ -404,6 +405,7 @@ int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, xdl_free_env(&xe2); return -1; } + status = 0; if (xscr1 || xscr2) { if (!xscr1) { result->ptr = xdl_malloc(mf2->size); @@ -413,14 +415,10 @@ int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, result->ptr = xdl_malloc(mf1->size); memcpy(result->ptr, mf1->ptr, mf1->size); result->size = mf1->size; - } else if (xdl_do_merge(&xe1, xscr1, name1, - &xe2, xscr2, name2, - level, xpp, result) < 0) { - xdl_free_script(xscr1); - xdl_free_script(xscr2); - xdl_free_env(&xe1); - xdl_free_env(&xe2); - return -1; + } else { + status = xdl_do_merge(&xe1, xscr1, name1, + &xe2, xscr2, name2, + level, xpp, result); } xdl_free_script(xscr1); xdl_free_script(xscr2); @@ -428,6 +426,5 @@ int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, xdl_free_env(&xe1); xdl_free_env(&xe2); - return 0; + return status; } - -- cgit v0.10.2-6-g49f6 From c2b4faea4522b28a7611ddab5eb6a5c221cc3ee5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 29 Nov 2006 12:07:37 -0800 Subject: merge-recursive: use xdl_merge(). This seem to pass the existing tests already. Signed-off-by: Junio C Hamano diff --git a/merge-recursive.c b/merge-recursive.c index cd2cc77..6e13b8e 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -21,6 +21,7 @@ #include "tag.h" #include "unpack-trees.h" #include "path-list.h" +#include "xdiff-interface.h" /* * A virtual commit has @@ -604,24 +605,15 @@ struct merge_file_info merge:1; }; -static char *git_unpack_file(const unsigned char *sha1, char *path) +static void fill_mm(const unsigned char *sha1, mmfile_t *mm) { - void *buf; - char type[20]; unsigned long size; - int fd; + char type[20]; - buf = read_sha1_file(sha1, type, &size); - if (!buf || strcmp(type, blob_type)) + mm->ptr = read_sha1_file(sha1, type, &size); + if (!mm->ptr || strcmp(type, blob_type)) die("unable to read blob object %s", sha1_to_hex(sha1)); - - strcpy(path, ".merge_file_XXXXXX"); - fd = mkstemp(path); - if (fd < 0) - die("unable to create temp-file"); - flush_buffer(fd, buf, size); - close(fd); - return path; + mm->size = size; } static struct merge_file_info merge_file(struct diff_filespec *o, @@ -652,49 +644,41 @@ static struct merge_file_info merge_file(struct diff_filespec *o, else if (sha_eq(b->sha1, o->sha1)) hashcpy(result.sha, a->sha1); else if (S_ISREG(a->mode)) { - int code = 1, fd; - struct stat st; - char orig[PATH_MAX]; - char src1[PATH_MAX]; - char src2[PATH_MAX]; - const char *argv[] = { - "merge", "-L", NULL, "-L", NULL, "-L", NULL, - NULL, NULL, NULL, - NULL - }; - char *la, *lb, *lo; - - git_unpack_file(o->sha1, orig); - git_unpack_file(a->sha1, src1); - git_unpack_file(b->sha1, src2); - - argv[2] = la = xstrdup(mkpath("%s/%s", branch1, a->path)); - argv[6] = lb = xstrdup(mkpath("%s/%s", branch2, b->path)); - argv[4] = lo = xstrdup(mkpath("orig/%s", o->path)); - argv[7] = src1; - argv[8] = orig; - argv[9] = src2, - - code = run_command_v(10, argv); - - free(la); - free(lb); - free(lo); - if (code && code < -256) { - die("Failed to execute 'merge'. merge(1) is used as the " - "file-level merge tool. Is 'merge' in your path?"); - } - fd = open(src1, O_RDONLY); - if (fd < 0 || fstat(fd, &st) < 0 || - index_fd(result.sha, fd, &st, 1, - "blob")) - die("Unable to add %s to database", src1); - - unlink(orig); - unlink(src1); - unlink(src2); - - result.clean = WEXITSTATUS(code) == 0; + mmfile_t orig, src1, src2; + mmbuffer_t result_buf; + xpparam_t xpp; + char *name1, *name2; + int merge_status; + + name1 = xstrdup(mkpath("%s/%s", branch1, a->path)); + name2 = xstrdup(mkpath("%s/%s", branch2, b->path)); + + fill_mm(o->sha1, &orig); + fill_mm(a->sha1, &src1); + fill_mm(b->sha1, &src2); + + memset(&xpp, 0, sizeof(xpp)); + merge_status = xdl_merge(&orig, + &src1, name1, + &src2, name2, + &xpp, XDL_MERGE_ZEALOUS, + &result_buf); + free(name1); + free(name2); + free(orig.ptr); + free(src1.ptr); + free(src2.ptr); + + if ((merge_status < 0) || !result_buf.ptr) + die("Failed to execute internal merge"); + + if (write_sha1_file(result_buf.ptr, result_buf.size, + blob_type, result.sha)) + die("Unable to add %s to database", + a->path); + + free(result_buf.ptr); + result.clean = (merge_status == 0); } else { if (!(S_ISLNK(a->mode) || S_ISLNK(b->mode))) die("cannot merge modes?"); -- cgit v0.10.2-6-g49f6 From 875b8ce47650d712c8f464cfedb9147673fe3ff7 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 30 Nov 2006 00:24:32 +0100 Subject: xdl_merge(): fix an off-by-one bug The line range is i1 .. (i1 + chg1 - 1), not i1 .. (i1 + chg1). Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c index 286f177..3f5dc87 100644 --- a/xdiff/xmerge.c +++ b/xdiff/xmerge.c @@ -194,11 +194,11 @@ static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m, * we have a very simple mmfile structure. */ t1.ptr = (char *)xe1->xdf2.recs[m->i1]->ptr; - t1.size = xe1->xdf2.recs[m->i1 + m->chg1]->ptr - + xe1->xdf2.recs[m->i1 + m->chg1]->size - t1.ptr; - t2.ptr = (char *)xe2->xdf2.recs[m->i1]->ptr; - t2.size = xe2->xdf2.recs[m->i1 + m->chg1]->ptr - + xe2->xdf2.recs[m->i1 + m->chg1]->size - t2.ptr; + t1.size = xe1->xdf2.recs[m->i1 + m->chg1 - 1]->ptr + + xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - t1.ptr; + t2.ptr = (char *)xe2->xdf2.recs[m->i2]->ptr; + t2.size = xe2->xdf2.recs[m->i2 + m->chg2 - 1]->ptr + + xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - t2.ptr; if (xdl_do_diff(&t1, &t2, xpp, &xe) < 0) return -1; if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 || -- cgit v0.10.2-6-g49f6 From 710daa83fc76f79b8f2ee9a765d297187c2c1aeb Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 30 Nov 2006 00:25:11 +0100 Subject: xdl_merge(): fix thinko If one side's block (of changed lines) ends later than the other side's block, the former should be tested against the next block of the other side, not vice versa. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c index 3f5dc87..1fe7a1b 100644 --- a/xdiff/xmerge.c +++ b/xdiff/xmerge.c @@ -318,13 +318,13 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, xscr1->i1 = i2; xscr1->i2 += xscr1->chg2; xscr1->chg2 = 0; - xscr1 = xscr1->next; + xscr2 = xscr2->next; } else if (i2 > i1) { xscr2->chg1 -= i2 - i1; xscr2->i1 = i1; xscr2->i2 += xscr2->chg2; xscr2->chg2 = 0; - xscr2 = xscr2->next; + xscr1 = xscr1->next; } else { xscr1 = xscr1->next; xscr2 = xscr2->next; -- cgit v0.10.2-6-g49f6 From aca085e577688108a2480b96a2f7077424a74e4d Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 3 Dec 2006 20:42:47 +0100 Subject: git-mv: search more precisely for source directory in index A move of a directory should find the entries in the index by searching for the name _including_ the slash. Otherwise, the directory can be shadowed by a file when it matches the prefix and is lexicographically smaller, e.g. "ab.c" shadows "ab/". Noticed by Sergey Vlasov. [jc: added Sergey's original reproduction recipe as a test case at the end of t7001.] Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/builtin-mv.c b/builtin-mv.c index 54dd3bf..d14a4a7 100644 --- a/builtin-mv.c +++ b/builtin-mv.c @@ -146,21 +146,24 @@ int cmd_mv(int argc, const char **argv, const char *prefix) && lstat(dst, &st) == 0) bad = "cannot move directory over file"; else if (src_is_dir) { + const char *src_w_slash = add_slash(src); + int len_w_slash = length + 1; int first, last; modes[i] = WORKING_DIRECTORY; - first = cache_name_pos(src, length); + first = cache_name_pos(src_w_slash, len_w_slash); if (first >= 0) - die ("Huh? %s/ is in index?", src); + die ("Huh? %.*s is in index?", + len_w_slash, src_w_slash); first = -1 - first; for (last = first; last < active_nr; last++) { const char *path = active_cache[last]->name; - if (strncmp(path, src, length) - || path[length] != '/') + if (strncmp(path, src_w_slash, len_w_slash)) break; } + free((char *)src_w_slash); if (last - first < 1) bad = "source directory is empty"; diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh index 23a1eff..2f4ff82 100755 --- a/t/t7001-mv.sh +++ b/t/t7001-mv.sh @@ -105,4 +105,17 @@ test_expect_success "Michael Cassar's test case" ' } ' +rm -fr papers partA path? + +test_expect_success "Sergey Vlasov's test case" ' + rm -fr .git && + git init-db && + mkdir ab && + date >ab.c && + date >ab/d && + git add ab.c ab && + git commit -m 'initial' && + git mv ab a +' + test_done -- cgit v0.10.2-6-g49f6 From 7c0f7028ee04f135c7481671f05ca4a66072c78f Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Mon, 4 Dec 2006 08:44:08 +0100 Subject: Set permissions of each new file before "cvs add"ing it. Otherwise, an executable script in git would end up being checked into the CVS repository without the execute bit. [jc: with an additional test script from Robin Rosenberg.] Signed-off-by: Jim Meyering Signed-off-by: Junio C Hamano diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index 7bac16e..c9d1d88 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -116,6 +116,7 @@ if ($opt_a) { close MSG; my (@afiles, @dfiles, @mfiles, @dirs); +my %amodes; my @files = safe_pipe_capture('git-diff-tree', '-r', $parent, $commit); #print @files; $? && die "Error in git-diff-tree"; @@ -124,6 +125,7 @@ foreach my $f (@files) { my @fields = split(m!\s+!, $f); if ($fields[4] eq 'A') { my $path = $fields[5]; + $amodes{$path} = $fields[1]; push @afiles, $path; # add any needed parent directories $path = dirname $path; @@ -268,6 +270,7 @@ if (($? >> 8) == 2) { } foreach my $f (@afiles) { + set_new_file_permissions($f, $amodes{$f}); if (grep { $_ eq $f } @bfiles) { system('cvs', 'add','-kb',$f); } else { @@ -342,3 +345,13 @@ sub safe_pipe_capture { } return wantarray ? @output : join('',@output); } + +# For any file we want to add to cvs, we must first set its permissions +# properly, *before* the "cvs add ..." command. Otherwise, it is impossible +# to change the permission of the file in the CVS repository using only cvs +# commands. This should be fixed in cvs-1.12.14. +sub set_new_file_permissions { + my ($file, $perm) = @_; + chmod oct($perm), $file + or die "failed to set permissions of \"$file\": $!\n"; +} diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh index 6e566d4..c102479 100755 --- a/t/t9200-git-cvsexportcommit.sh +++ b/t/t9200-git-cvsexportcommit.sh @@ -142,4 +142,20 @@ test_expect_success \ diff F/newfile6.png ../F/newfile6.png )' +test_expect_success 'Retain execute bit' ' + mkdir G && + echo executeon >G/on && + chmod +x G/on && + echo executeoff >G/off && + git add G/on && + git add G/off && + git commit -a -m "Execute test" && + ( + cd "$CVSWORK" && + git-cvsexportcommit -c HEAD + test -x G/on && + ! test -x G/off + ) +' + test_done -- cgit v0.10.2-6-g49f6 From 396db813f2e9b56460cd783c73daf15150e36b41 Mon Sep 17 00:00:00 2001 From: David Miller Date: Sun, 3 Dec 2006 23:17:00 -0800 Subject: Pass -M to diff in request-pull Linus recommended this, otherwise any renames cause the diffstat output to be ridiculous in some circumstances. Because the corresponding "git-pull" done when the requestee actually makes pull shows the stat with rename detection enabled, it makes sense to match what the request message includes to that output, to make the result easier to verify. Signed-off-by: David S. Miller Signed-off-by: Junio C Hamano diff --git a/git-request-pull.sh b/git-request-pull.sh index 4319e35..4eacc3a 100755 --- a/git-request-pull.sh +++ b/git-request-pull.sh @@ -30,4 +30,4 @@ echo " $url" echo git log $baserev..$headrev | git-shortlog ; -git diff --stat --summary $baserev..$headrev +git diff -M --stat --summary $baserev..$headrev -- cgit v0.10.2-6-g49f6 From f848718a6980ebda0eb5afb2ca49c3bc1e7b2b1d Mon Sep 17 00:00:00 2001 From: Alex Riesen Date: Mon, 4 Dec 2006 10:50:04 +0100 Subject: Make perl/ build procedure ActiveState friendly. On Cygwin + ActivateState Perl, Makefile generated with MakeMaker is not usable because of line-endings and back-slashes. This teaches perl/Makefile to write a handcrafted equivalent perl.mak file with 'make NO_PERL_MAKEMAKER=NoThanks'. Signed-off-by: Alex Riesen Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index de3e9f3..a1861de 100644 --- a/Makefile +++ b/Makefile @@ -91,6 +91,10 @@ all: # # Define USE_STDEV below if you want git to care about the underlying device # change being considered an inode change from the update-cache perspective. +# +# Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's +# MakeMaker (e.g. using ActiveState under Cygwin). +# GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE @$(SHELL_PATH) ./GIT-VERSION-GEN @@ -539,6 +543,9 @@ endif ifdef NO_ACCURATE_DIFF BASIC_CFLAGS += -DNO_ACCURATE_DIFF endif +ifdef NO_PERL_MAKEMAKER + export NO_PERL_MAKEMAKER +endif # Shell quote (do not use $(call) to accommodate ancient setups); @@ -568,8 +575,8 @@ export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi -all: perl/Makefile - $(MAKE) -C perl +all: + $(MAKE) -C perl PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all $(MAKE) -C templates strip: $(PROGRAMS) git$X @@ -602,7 +609,11 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh chmod +x $@+ mv $@+ $@ -$(patsubst %.perl,%,$(SCRIPT_PERL)): perl/Makefile +$(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak + +perl/perl.mak: GIT-CFLAGS + $(MAKE) -C perl PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' $(@F) + $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl rm -f $@ $@+ INSTLIBDIR=`$(MAKE) -C perl -s --no-print-directory instlibdir` && \ @@ -796,7 +807,7 @@ install: all $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL) git$X gitk '$(DESTDIR_SQ)$(bindir_SQ)' $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install - $(MAKE) -C perl install + $(MAKE) -C perl prefix='$(prefix_SQ)' install if test 'z$(bindir_SQ)' != 'z$(gitexecdir_SQ)'; \ then \ ln -f '$(DESTDIR_SQ)$(bindir_SQ)/git$X' \ @@ -866,8 +877,7 @@ clean: rm -f $(htmldocs).tar.gz $(manpages).tar.gz rm -f gitweb/gitweb.cgi $(MAKE) -C Documentation/ clean - [ ! -f perl/Makefile ] || $(MAKE) -C perl/ clean || $(MAKE) -C perl/ clean - rm -f perl/ppport.h perl/Makefile.old + $(MAKE) -C perl clean $(MAKE) -C templates/ clean $(MAKE) -C t/ clean rm -f GIT-VERSION-FILE GIT-CFLAGS diff --git a/perl/.gitignore b/perl/.gitignore index e990cae..98b2477 100644 --- a/perl/.gitignore +++ b/perl/.gitignore @@ -1,4 +1,5 @@ -Makefile +perl.mak +perl.mak.old blib blibdirs pm_to_blib diff --git a/perl/Makefile b/perl/Makefile new file mode 100644 index 0000000..bd483b0 --- /dev/null +++ b/perl/Makefile @@ -0,0 +1,39 @@ +# +# Makefile for perl support modules and routine +# +makfile:=perl.mak + +PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) +prefix_SQ = $(subst ','\'',$(prefix)) + +all install instlibdir: $(makfile) + $(MAKE) -f $(makfile) $@ + +clean: + test -f $(makfile) && $(MAKE) -f $(makfile) $@ || exit 0 + $(RM) ppport.h + $(RM) $(makfile) + $(RM) $(makfile).old + +ifdef NO_PERL_MAKEMAKER +instdir_SQ = $(subst ','\'',$(prefix)/lib) +$(makfile): ../GIT-CFLAGS Makefile + echo all: > $@ + echo ' :' >> $@ + echo install: >> $@ + echo ' mkdir -p $(instdir_SQ)' >> $@ + echo ' $(RM) $(instdir_SQ)/Git.pm; cp Git.pm $(instdir_SQ)' >> $@ + echo ' $(RM) $(instdir_SQ)/Error.pm; \ + cp private-Error.pm $(instdir_SQ)/Error.pm' >> $@ + echo instlibdir: >> $@ + echo ' echo $(instdir_SQ)' >> $@ +else +$(makfile): Makefile.PL ../GIT-CFLAGS + '$(PERL_PATH_SQ)' $< FIRST_MAKEFILE='$@' PREFIX='$(prefix_SQ)' +endif + +# this is just added comfort for calling make directly in perl dir +# (even though GIT-CFLAGS aren't used yet. If ever) +../GIT-CFLAGS: + $(MAKE) -C .. GIT-CFLAGS + -- cgit v0.10.2-6-g49f6 From e1147267afc0afa269884767c4045847d9a2be8a Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 4 Dec 2006 14:09:43 +0100 Subject: gitweb: Fix Atom feed : it is $logo, not $logo_url Fix contents of Atom feed element; it should be URL of $logo, not URL pointed by logo link. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 093bd72..ffe8ce1 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -4282,7 +4282,7 @@ XML } if (defined $logo_url) { # not twice as wide as tall: 72 x 27 pixels - print "" . esc_url($logo_url) . "\n"; + print "" . esc_url($logo) . "\n"; } if (! %latest_date) { # dummy date to keep the feed valid until commits trickle in: -- cgit v0.10.2-6-g49f6 From b360cca0b100e14abffa4cae78521b493c783738 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 4 Dec 2006 14:29:09 +0100 Subject: git-clone: Rename --use-immingled-remote option to --no-separate-remote With making --use-separate-remote default when creating non-bare clone, there was need for the flag which would turn off this behavior. It was called --use-immingled-remote. Immingle means to blend, to combine into one, to intermingle, but it is a bit obscure word. I think it would be better to use simply --no-separate-remote as the opposite to --use-separate-remote option to git clone. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index 4cb4223..d5efa00 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -11,7 +11,7 @@ SYNOPSIS [verse] 'git-clone' [--template=] [-l [-s]] [-q] [-n] [--bare] [-o ] [-u ] [--reference ] - [--use-separate-remote | --use-immingled-remote] + [--use-separate-remote | --no-separate-remote] [] DESCRIPTION @@ -105,7 +105,7 @@ OPTIONS of `$GIT_DIR/refs/heads/`. Only the local master branch is saved in the latter. This is the default. ---use-immingled-remote:: +--no-separate-remote:: Save remotes heads in the same namespace as the local heads, `$GIT_DIR/refs/heads/'. In regular repositories, this is a legacy setup git-clone created by default in diff --git a/git-clone.sh b/git-clone.sh index d4ee93f..8964039 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -14,7 +14,7 @@ die() { } usage() { - die "Usage: $0 [--template=] [--use-immingled-remote] [--reference ] [--bare] [-l [-s]] [-q] [-u ] [--origin ] [-n] []" + die "Usage: $0 [--template=] [--no-separate-remote] [--reference ] [--bare] [-l [-s]] [-q] [-u ] [--origin ] [-n] []" } get_repo_base() { @@ -140,7 +140,7 @@ while *,--use-separate-remote) # default use_separate_remote=t ;; - *,--use-immingled-remote) + *,--no-separate-remote) use_separate_remote= ;; 1,--reference) usage ;; *,--reference) @@ -176,7 +176,7 @@ repo="$1" test -n "$repo" || die 'you must specify a repository to clone.' -# --bare implies --no-checkout and --use-immingled-remote +# --bare implies --no-checkout and --no-separate-remote if test yes = "$bare" then if test yes = "$origin_override" -- cgit v0.10.2-6-g49f6 From 4cd75359ad5d4c90ba6ae6d68ffb6d00e5092b8a Mon Sep 17 00:00:00 2001 From: Michael Loeffler Date: Mon, 4 Dec 2006 20:34:34 +0100 Subject: git-fetch: ignore dereferenced tags in expand_refs_wildcard There was a little bug in the brace expansion which should remove the ^{} from the tagname. It used ${name#'^{}'} instead of $(name%'^{}'}, the difference is that '#' will remove the given pattern only from the beginning of a string and '%' only from the end of a string. Signed-off-by: Michael Loeffler Signed-off-by: Junio C Hamano diff --git a/git-parse-remote.sh b/git-parse-remote.sh index 19bc385..da064a5 100755 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -116,7 +116,7 @@ expand_refs_wildcard () { while read sha1 name do mapped=${name#"$from"} - if test "z$name" != "z${name#'^{}'}" || + if test "z$name" != "z${name%'^{}'}" || test "z$name" = "z$mapped" then continue -- cgit v0.10.2-6-g49f6 From 562cefbdbfaeb92f91c961c67960a93a7772220c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 4 Dec 2006 14:24:12 -0800 Subject: receive-pack: do not insist on fast-forward outside refs/heads/ Especially refs/tags/ hierarchy should match what git-fetch checks. Signed-off-by: Junio C Hamano diff --git a/receive-pack.c b/receive-pack.c index d56898c..f189151 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -118,7 +118,8 @@ static int update(struct command *cmd) return error("unpack should have generated %s, " "but I can't find it!", new_hex); } - if (deny_non_fast_forwards && !is_null_sha1(old_sha1)) { + if (deny_non_fast_forwards && !is_null_sha1(old_sha1) && + !strncmp(name, "refs/heads/", 11)) { struct commit *old_commit, *new_commit; struct commit_list *bases, *ent; -- cgit v0.10.2-6-g49f6 From 0fb1eaa8850557249a8d1c43a4f0f3ac5a5f75ce Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 4 Dec 2006 02:11:39 -0800 Subject: unpack-trees: make sure "df_conflict_entry.name" is NUL terminated. The structure that ends with a flexible array member (or 0 length array with older GCC) "char name[FLEX_ARRAY]" is allocated on the stack and we use it after clearing its entire size with memset. That does not guarantee that "name" is properly NUL terminated as we intended on platforms with more forgiving structure alignment requirements. Reported breakage on m68k by Roman Zippel. Signed-off-by: Junio C Hamano diff --git a/unpack-trees.c b/unpack-trees.c index 7cfd628..47aa804 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -370,7 +370,7 @@ int unpack_trees(struct object_list *trees, struct unpack_trees_options *o) int i; struct object_list *posn = trees; struct tree_entry_list df_conflict_list; - struct cache_entry df_conflict_entry; + static struct cache_entry *dfc; memset(&df_conflict_list, 0, sizeof(df_conflict_list)); df_conflict_list.next = &df_conflict_list; @@ -381,8 +381,10 @@ int unpack_trees(struct object_list *trees, struct unpack_trees_options *o) state.refresh_cache = 1; o->merge_size = len; - memset(&df_conflict_entry, 0, sizeof(df_conflict_entry)); - o->df_conflict_entry = &df_conflict_entry; + + if (!dfc) + dfc = xcalloc(1, sizeof(struct cache_entry) + 1); + o->df_conflict_entry = dfc; if (len) { posns = xmalloc(len * sizeof(struct tree_entry_list *)); -- cgit v0.10.2-6-g49f6 From c7c24889bb694f93a12f41a28fab26d30e571c17 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 3 Dec 2006 19:25:34 +0100 Subject: diff -b: ignore whitespace at end of line This is _not_ the same as "treat eol as whitespace", since that would mean that multiple empty lines would be treated as equal to e.g. a space. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index 1bc5b7a..adf4993 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -109,12 +109,10 @@ index d99af23..8b32fb5 100644 + whitespace at beginning whitespace change -whitespace in the middle --whitespace at end +white space in the middle -+whitespace at end + whitespace at end unchanged line --CR at endQ -+CR at end + CR at endQ EOF git-diff -b > out test_expect_success 'another test, with -b' 'diff -u expect out' diff --git a/xdiff/xutils.c b/xdiff/xutils.c index 9e4bb47..1b899f3 100644 --- a/xdiff/xutils.c +++ b/xdiff/xutils.c @@ -230,7 +230,8 @@ unsigned long xdl_hash_record(char const **data, char const *top, long flags) { while (ptr + 1 < top && isspace(ptr[1]) && ptr[1] != '\n') ptr++; - if (flags & XDF_IGNORE_WHITESPACE_CHANGE) { + if (flags & XDF_IGNORE_WHITESPACE_CHANGE + && ptr[1] != '\n') { ha += (ha << 5); ha ^= (unsigned long) ' '; } -- cgit v0.10.2-6-g49f6 From 8ebe185bbf3f1f4f59bcc61e3d1849a76f6af983 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 3 Dec 2006 17:24:41 +0100 Subject: Document git-diff whitespace flags -b and -w Document git diff options -b / --ignore-space-change and -w / --ignore-all-space, introduced by Johannes Schindelin in commit 0d21efa5, "Teach diff about -b and -w flags". The description of options is taken from GNU diff man page and GNU Diffutils info documentation. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index e112172..9cdd171 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -129,5 +129,21 @@ -a:: Shorthand for "--text". +--ignore-space-change:: + Ignore changes in amount of white space. This ignores white + space at line end, and consider all other sequences of one or + more white space characters to be equivalent. + +-b:: + Shorthand for "--ignore-space-change". + +--ignore-all-space:: + Ignore white space when comparing lines. This ignores + difference even if one line has white space where the other + line has none. + +-w:: + Shorthand for "--ignore-all-space". + For more detailed explanation on these common options, see also link:diffcore.html[diffcore documentation]. -- cgit v0.10.2-6-g49f6 From 366bfcb68f4d98a43faaf17893a1aa0a7a9e2c58 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Mon, 4 Dec 2006 11:13:39 -0500 Subject: make 'git add' a first class user friendly interface to the index This brings the power of the index up front using a proper mental model without talking about the index at all. See for example how all the technical discussion has been evacuated from the git-add man page. Any content to be committed must be added together. Whether that content comes from new files or modified files doesn't matter. You just need to "add" it, either with git-add, or by providing git-commit with -a (for already known files only of course). No need for a separate command to distinguish new vs modified files please. That would only screw the mental model everybody should have when using GIT. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index 6342ea3..d86c0e7 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -3,7 +3,7 @@ git-add(1) NAME ---- -git-add - Add files to the index file +git-add - Add file contents to the changeset to be committed next SYNOPSIS -------- @@ -11,16 +11,31 @@ SYNOPSIS DESCRIPTION ----------- -A simple wrapper for git-update-index to add files to the index, -for people used to do "cvs add". +All the changed file contents to be committed together in a single set +of changes must be "added" with the 'add' command before using the +'commit' command. This is not only for adding new files. Even modified +files must be added to the set of changes about to be committed. -It only adds non-ignored files, to add ignored files use +This command can be performed multiple times before a commit. The added +content corresponds to the state of specified file(s) at the time the +'add' command is used. This means the 'commit' command will not consider +subsequent changes to already added content if it is not added again before +the commit. + +The 'git status' command can be used to obtain a summary of what is included +for the next commit. + +This command only adds non-ignored files, to add ignored files use "git update-index --add". +Please see gitlink:git-commit[1] for alternative ways to add content to a +commit. + + OPTIONS ------- ...:: - Files to add to the index (see gitlink:git-ls-files[1]). + Files to add content from. -n:: Don't actually add the file(s), just show if they exist. @@ -34,27 +49,12 @@ OPTIONS for command-line options). -DISCUSSION ----------- - -The list of given to the command is fed to `git-ls-files` -command to list files that are not registered in the index and -are not ignored/excluded by `$GIT_DIR/info/exclude` file or -`.gitignore` file in each directory. This means two things: - -. You can put the name of a directory on the command line, and - the command will add all files in it and its subdirectories; - -. Giving the name of a file that is already in index does not - run `git-update-index` on that path. - - EXAMPLES -------- git-add Documentation/\\*.txt:: - Adds all `\*.txt` files that are not in the index under - `Documentation` directory and its subdirectories. + Adds content from all `\*.txt` files under `Documentation` + directory and its subdirectories. + Note that the asterisk `\*` is quoted from the shell in this example; this lets the command to include the files from @@ -62,15 +62,18 @@ subdirectories of `Documentation/` directory. git-add git-*.sh:: - Adds all git-*.sh scripts that are not in the index. + Considers adding content from all git-*.sh scripts. Because this example lets shell expand the asterisk (i.e. you are listing the files explicitly), it does not - add `subdir/git-foo.sh` to the index. + consider `subdir/git-foo.sh`. See Also -------- +gitlink:git-status[1] gitlink:git-rm[1] -gitlink:git-ls-files[1] +gitlink:git-mv[1] +gitlink:git-commit[1] +gitlink:git-update-index[1] Author ------ diff --git a/Documentation/tutorial.txt b/Documentation/tutorial.txt index fe4491d..02dede3 100644 --- a/Documentation/tutorial.txt +++ b/Documentation/tutorial.txt @@ -87,14 +87,48 @@ thorough description. Tools that turn commits into email, for example, use the first line on the Subject line and the rest of the commit in the body. -To add a new file, first create the file, then ------------------------------------------------- -$ git add path/to/new/file ------------------------------------------------- +Git tracks content not files +---------------------------- + +With git you have to explicitly "add" all the changed _content_ you +want to commit together. This can be done in a few different ways: + +1) By using 'git add ...' + + This can be performed multiple times before a commit. Note that this + is not only for adding new files. Even modified files must be + added to the set of changes about to be committed. The "git status" + command gives you a summary of what is included so far for the + next commit. When done you should use the 'git commit' command to + make it real. + + Note: don't forget to 'add' a file again if you modified it after the + first 'add' and before 'commit'. Otherwise only the previous added + state of that file will be committed. This is because git tracks + content, so what you're really 'add'ing to the commit is the *content* + of the file in the state it is in when you 'add' it. + +2) By using 'git commit -a' directly + + This is a quick way to automatically 'add' the content from all files + that were modified since the previous commit, and perform the actual + commit without having to separately 'add' them beforehand. This will + not add content from new files i.e. files that were never added before. + Those files still have to be added explicitly before performing a + commit. + +But here's a twist. If you do 'git commit ...' then only +the changes belonging to those explicitly specified files will be +committed, entirely bypassing the current "added" changes. Those "added" +changes will still remain available for a subsequent commit though. + +However, for normal usage you only have to remember 'git add' + 'git commit' +and/or 'git commit -a'. + -then commit as usual. No special command is required when removing a -file; just remove it, then tell `commit` about the file as usual. +Viewing the changelog +--------------------- At any point you can view the history of your changes using diff --git a/builtin-add.c b/builtin-add.c index febb75e..b3f9206 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -94,9 +94,6 @@ int cmd_add(int argc, const char **argv, const char *prefix) newfd = hold_lock_file_for_update(&lock_file, get_index_file(), 1); - if (read_cache() < 0) - die("index file corrupt"); - for (i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -131,6 +128,9 @@ int cmd_add(int argc, const char **argv, const char *prefix) return 0; } + if (read_cache() < 0) + die("index file corrupt"); + for (i = 0; i < dir.nr; i++) add_file_to_index(dir.entries[i]->name, verbose); diff --git a/wt-status.c b/wt-status.c index de1be5b..4b8b570 100644 --- a/wt-status.c +++ b/wt-status.c @@ -163,7 +163,7 @@ static void wt_status_print_changed_cb(struct diff_queue_struct *q, int i; if (q->nr) wt_status_print_header("Changed but not updated", - "use git-update-index to mark for commit"); + "use git-add on files to include for commit"); for (i = 0; i < q->nr; i++) wt_status_print_filepair(WT_STATUS_CHANGED, q->queue[i]); if (q->nr) -- cgit v0.10.2-6-g49f6 From 98e6da8a360b77af2924e8056fd951013835699b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 5 Dec 2006 22:15:35 +0100 Subject: xdl_merge(): fix and simplify conflict handling Suppose you have changes in new1 to the original lines 10-20, and changes in new2 to the original lines 15-25, then the changes to 10-25 conflict. But it is possible that the next changes in new1 still overlap with this change to new2. So, in the next iteration we have to look at the same change to new2 again. The old code tried to be a bit too clever. The new code is shorter and more to the point: do not fiddle with the ranges at all. Also, xdl_append_merge() tries harder to combine conflicts. This is necessary, because with the above simplification, some conflicts would not be recognized as conflicts otherwise: In the above scenario, it is possible that there is no other change to new1. Absent the combine logic, the change in new2 would be recorded _again_, but as a non-conflict. Signed-off-by: Johannes Schindelin diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c index 1fe7a1b..352207e 100644 --- a/xdiff/xmerge.c +++ b/xdiff/xmerge.c @@ -38,8 +38,9 @@ static int xdl_append_merge(xdmerge_t **merge, int mode, long i1, long chg1, long i2, long chg2) { xdmerge_t *m = *merge; - if (m && mode == m->mode && - (i1 == m->i1 + m->chg1 || i2 == m->i2 + m->chg2)) { + if (m && (i1 <= m->i1 + m->chg1 || i2 <= m->i2 + m->chg2)) { + if (mode != m->mode) + m->mode = 0; m->chg1 = i1 + chg1 - m->i1; m->chg2 = i2 + chg2 - m->i2; } else { @@ -313,22 +314,10 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, i1 = xscr1->i1 + xscr1->chg1; i2 = xscr2->i1 + xscr2->chg1; - if (i1 > i2) { - xscr1->chg1 -= i1 - i2; - xscr1->i1 = i2; - xscr1->i2 += xscr1->chg2; - xscr1->chg2 = 0; + if (i1 >= i2) xscr2 = xscr2->next; - } else if (i2 > i1) { - xscr2->chg1 -= i2 - i1; - xscr2->i1 = i1; - xscr2->i2 += xscr2->chg2; - xscr2->chg2 = 0; - xscr1 = xscr1->next; - } else { + if (i2 >= i1) xscr1 = xscr1->next; - xscr2 = xscr2->next; - } } while (xscr1) { if (!changes) -- cgit v0.10.2-6-g49f6 From f8a9d4287277ed15d3f0d61004f4510c59f1f392 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 4 Dec 2006 16:00:46 -0800 Subject: read-tree: further loosen "working file will be lost" check. This follows up commit ed93b449 where we removed overcautious "working file will be lost" check. A new option "--exclude-per-directory=.gitignore" can be used to tell the "git-read-tree" command that the user does not mind losing contents in untracked files in the working tree, if they need to be overwritten by a merge (either a two-way "switch branches" merge, or a three-way merge). Signed-off-by: Junio C Hamano diff --git a/builtin-read-tree.c b/builtin-read-tree.c index c1867d2..3f6cae3 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -10,6 +10,7 @@ #include "tree-walk.h" #include "cache-tree.h" #include "unpack-trees.h" +#include "dir.h" #include "builtin.h" static struct object_list *trees; @@ -178,6 +179,23 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) continue; } + if (!strncmp(arg, "--exclude-per-directory=", 24)) { + struct dir_struct *dir; + + if (opts.dir) + die("more than one --exclude-per-directory are given."); + + dir = calloc(1, sizeof(*opts.dir)); + dir->show_ignored = 1; + dir->exclude_per_dir = arg + 24; + opts.dir = dir; + /* We do not need to nor want to do read-directory + * here; we are merely interested in reusing the + * per directory ignore stack mechanism. + */ + continue; + } + /* using -u and -i at the same time makes no sense */ if (1 < opts.index_only + opts.update) usage(read_tree_usage); @@ -190,6 +208,8 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) } if ((opts.update||opts.index_only) && !opts.merge) usage(read_tree_usage); + if ((opts.dir && !opts.update)) + die("--exclude-per-directory is meaningless unless -u"); if (opts.prefix) { int pfxlen = strlen(opts.prefix); diff --git a/dir.c b/dir.c index 96389b3..e6a61ee 100644 --- a/dir.c +++ b/dir.c @@ -156,7 +156,7 @@ void add_excludes_from_file(struct dir_struct *dir, const char *fname) die("cannot use %s as an exclude file", fname); } -static int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen) +int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen) { char exclude_file[PATH_MAX]; struct exclude_list *el = &dir->exclude_list[EXC_DIRS]; @@ -170,7 +170,7 @@ static int push_exclude_per_directory(struct dir_struct *dir, const char *base, return current_nr; } -static void pop_exclude_per_directory(struct dir_struct *dir, int stk) +void pop_exclude_per_directory(struct dir_struct *dir, int stk) { struct exclude_list *el = &dir->exclude_list[EXC_DIRS]; diff --git a/dir.h b/dir.h index 313f8ab..550551a 100644 --- a/dir.h +++ b/dir.h @@ -43,6 +43,9 @@ extern int common_prefix(const char **pathspec); extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen); extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen); +extern int push_exclude_per_directory(struct dir_struct *, const char *, int); +extern void pop_exclude_per_directory(struct dir_struct *, int); + extern int excluded(struct dir_struct *, const char *); extern void add_excludes_from_file(struct dir_struct *, const char *fname); extern void add_exclude(const char *string, const char *base, diff --git a/t/t1004-read-tree-m-u-wf.sh b/t/t1004-read-tree-m-u-wf.sh index 018fbea..4f664f6 100755 --- a/t/t1004-read-tree-m-u-wf.sh +++ b/t/t1004-read-tree-m-u-wf.sh @@ -8,23 +8,27 @@ test_description='read-tree -m -u checks working tree files' test_expect_success 'two-way setup' ' + mkdir subdir && echo >file1 file one && echo >file2 file two && - git update-index --add file1 file2 && + echo >subdir/file1 file one in subdirectory && + echo >subdir/file2 file two in subdirectory && + git update-index --add file1 file2 subdir/file1 subdir/file2 && git commit -m initial && git branch side && git tag -f branch-point && echo file2 is not tracked on the master anymore && - rm -f file2 && - git update-index --remove file2 && - git commit -a -m "master removes file2" + rm -f file2 subdir/file2 && + git update-index --remove file2 subdir/file2 && + git commit -a -m "master removes file2 and subdir/file2" ' test_expect_success 'two-way not clobbering' ' echo >file2 master creates untracked file2 && + echo >subdir/file2 master creates untracked subdir/file2 && if err=`git read-tree -m -u master side 2>&1` then echo should have complained @@ -34,20 +38,82 @@ test_expect_success 'two-way not clobbering' ' fi ' +echo file2 >.gitignore + +test_expect_success 'two-way with incorrect --exclude-per-directory (1)' ' + + if err=`git read-tree -m --exclude-per-directory=.gitignore master side 2>&1` + then + echo should have complained + false + else + echo "happy to see $err" + fi +' + +test_expect_success 'two-way with incorrect --exclude-per-directory (2)' ' + + if err=`git read-tree -m -u --exclude-per-directory=foo --exclude-per-directory=.gitignore master side 2>&1` + then + echo should have complained + false + else + echo "happy to see $err" + fi +' + +test_expect_success 'two-way clobbering a ignored file' ' + + git read-tree -m -u --exclude-per-directory=.gitignore master side +' + +rm -f .gitignore + # three-tree test -test_expect_success 'three-way not complaining' ' +test_expect_success 'three-way not complaining on an untracked path in both' ' - rm -f file2 && + rm -f file2 subdir/file2 && git checkout side && echo >file3 file three && - git update-index --add file3 && - git commit -a -m "side adds file3" && + echo >subdir/file3 file three && + git update-index --add file3 subdir/file3 && + git commit -a -m "side adds file3 and removes file2" && git checkout master && echo >file2 file two is untracked on the master side && + echo >subdir/file2 file two is untracked on the master side && git-read-tree -m -u branch-point master side ' +test_expect_success 'three-way not cloberring a working tree file' ' + + git reset --hard && + rm -f file2 subdir/file2 file3 subdir/file3 && + git checkout master && + echo >file3 file three created in master, untracked && + echo >subdir/file3 file three created in master, untracked && + if err=`git read-tree -m -u branch-point master side 2>&1` + then + echo should have complained + false + else + echo "happy to see $err" + fi +' + +echo >.gitignore file3 + +test_expect_success 'three-way not complaining on an untracked file' ' + + git reset --hard && + rm -f file2 subdir/file2 file3 subdir/file3 && + git checkout master && + echo >file3 file three created in master, untracked && + echo >subdir/file3 file three created in master, untracked && + + git read-tree -m -u --exclude-per-directory=.gitignore branch-point master side +' + test_done diff --git a/unpack-trees.c b/unpack-trees.c index 7cfd628..79d21e2 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -1,6 +1,7 @@ #include #include #include "cache.h" +#include "dir.h" #include "tree.h" #include "tree-walk.h" #include "cache-tree.h" @@ -77,6 +78,12 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, { int baselen = strlen(base); int src_size = len + 1; + int i_stk = i_stk; + int retval = 0; + + if (o->dir) + i_stk = push_exclude_per_directory(o->dir, base, strlen(base)); + do { int i; const char *first; @@ -143,7 +150,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, } /* No name means we're done */ if (!first) - return 0; + goto leave_directory; pathlen = strlen(first); ce_size = cache_entry_size(baselen + pathlen); @@ -240,13 +247,20 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len, newbase[baselen + pathlen] = '/'; newbase[baselen + pathlen + 1] = '\0'; if (unpack_trees_rec(subposns, len, newbase, o, - indpos, df_conflict_list)) - return -1; + indpos, df_conflict_list)) { + retval = -1; + goto leave_directory; + } free(newbase); } free(subposns); free(src); } while (1); + + leave_directory: + if (o->dir) + pop_exclude_per_directory(o->dir, i_stk); + return retval; } /* Unlink the last component and attempt to remove leading @@ -456,7 +470,7 @@ static void invalidate_ce_path(struct cache_entry *ce) /* * We do not want to remove or overwrite a working tree file that - * is not tracked. + * is not tracked, unless it is ignored. */ static void verify_absent(const char *path, const char *action, struct unpack_trees_options *o) @@ -465,7 +479,7 @@ static void verify_absent(const char *path, const char *action, if (o->index_only || o->reset || !o->update) return; - if (!lstat(path, &st)) + if (!lstat(path, &st) && !(o->dir && excluded(o->dir, path))) die("Untracked working tree file '%s' " "would be %s by merge.", path, action); } diff --git a/unpack-trees.h b/unpack-trees.h index c460162..191f744 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -16,6 +16,7 @@ struct unpack_trees_options { int verbose_update; int aggressive; const char *prefix; + struct dir_struct *dir; merge_fn_t fn; int head_idx; -- cgit v0.10.2-6-g49f6 From 1127148089234a6f84754f2f0ec36cbbcae06fa9 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 4 Dec 2006 16:07:57 -0800 Subject: Loosen "working file will be lost" check in Porcelain-ish This uses the previous update to read-tree in Porcelain-ish commands "git checkout" and "git merge" to loosen the check when switching branches. Signed-off-by: Junio C Hamano diff --git a/git-checkout.sh b/git-checkout.sh index 737abd0..4192a99 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -161,7 +161,7 @@ then git-read-tree --reset -u $new else git-update-index --refresh >/dev/null - merge_error=$(git-read-tree -m -u $old $new 2>&1) || ( + merge_error=$(git-read-tree -m -u --exclude-per-directory=.gitignore $old $new 2>&1) || ( case "$merge" in '') echo >&2 "$merge_error" @@ -172,7 +172,8 @@ else git diff-files --name-only | git update-index --remove --stdin && work=`git write-tree` && git read-tree --reset -u $new && - git read-tree -m -u --aggressive $old $new $work || exit + git read-tree -m -u --aggressive --exclude-per-directory=.gitignore $old $new $work || + exit if result=`git write-tree 2>/dev/null` then diff --git a/git-merge.sh b/git-merge.sh index 272f004..397b33f 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -264,7 +264,7 @@ f,*) echo "Updating $(git-rev-parse --short $head)..$(git-rev-parse --short $1)" git-update-index --refresh 2>/dev/null new_head=$(git-rev-parse --verify "$1^0") && - git-read-tree -u -v -m $head "$new_head" && + git-read-tree -v -m -u --exclude-per-directory=.gitignore $head "$new_head" && finish "$new_head" "Fast forward" dropsave exit 0 -- cgit v0.10.2-6-g49f6 From 22f741dab7cc93f00f7cf96cd65d7481677cd855 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 5 Dec 2006 23:44:23 -0800 Subject: read-tree: document --exclude-per-directory This documents the new option to read-tree that is used for the improved "branch switching" code. Signed-off-by: Junio C Hamano diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 11bd9c0..0ff2890 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -8,7 +8,7 @@ git-read-tree - Reads tree information into the index SYNOPSIS -------- -'git-read-tree' ( | [[-m [--aggressive] | --reset | --prefix=] [-u | -i]] [ []]) +'git-read-tree' ( | [[-m [--aggressive] | --reset | --prefix=] [-u | -i]] [--exclude-per-directory=] [ []]) DESCRIPTION @@ -71,6 +71,20 @@ OPTIONS directory. Note that the `/` value must end with a slash. +--exclude-per-directory=:: + When running the command with `-u` and `-m` options, the + merge result may need to overwrite paths that are not + tracked in the current branch. The command usually + refuses to proceed with the merge to avoid losing such a + path. However this safety valve sometimes gets in the + way. For example, it often happens that the other + branch added a file that used to be a generated file in + your branch, and the safety valve triggers when you try + to switch to that branch after you ran `make` but before + running `make clean` to remove the generated file. This + option tells the command to read per-directory exclude + file (usually '.gitignore') and allows such an untracked + but explicitly ignored file to be overwritten. :: The id of the tree object(s) to be read/merged. diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 3f6cae3..8ba436d 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -85,7 +85,7 @@ static void prime_cache_tree(void) } -static const char read_tree_usage[] = "git-read-tree ( | [[-m [--aggressive] | --reset | --prefix=] [-u | -i]] [ []])"; +static const char read_tree_usage[] = "git-read-tree ( | [[-m [--aggressive] | --reset | --prefix=] [-u | -i]] [--exclude-per-directory=] [ []])"; static struct lock_file lock_file; -- cgit v0.10.2-6-g49f6 From c976d415e5352886f0650f8e2edba81866c38587 Mon Sep 17 00:00:00 2001 From: Lars Hjemli Date: Tue, 28 Nov 2006 15:47:40 +0100 Subject: git-branch: add options and tests for branch renaming Extend git-branch with the following options: git-branch -m|-M [] newbranch The -M variation is required to force renaming over an exsisting branchname. This also indroduces $GIT_DIR/RENAME_REF which is a "metabranch" used when renaming branches. It will always hold the original sha1 for the latest renamed branch. Additionally, if $GIT_DIR/logs/RENAME_REF exists, all branch rename events are logged there. Finally, some testcases are added to verify the new options. Signed-off-by: Lars Hjemli Signed-off-by: Junio C Hamano diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 4f5b5d5..71417fe 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -8,8 +8,9 @@ git-branch - List, create, or delete branches. SYNOPSIS -------- [verse] -'git-branch' [-r] [-a] [-v] [--abbrev=] +'git-branch' [-r | -a] [-v [--abbrev=]] 'git-branch' [-l] [-f] [] +'git-branch' (-m | -M) [] 'git-branch' (-d | -D) ... DESCRIPTION @@ -24,6 +25,12 @@ It will start out with a head equal to the one given as . If no is given, the branch will be created with a head equal to that of the currently checked out branch. +With a '-m' or '-M' option, will be renamed to . +If had a corresponding reflog, it is renamed to match +, and a reflog entry is created to remember the branch +renaming. If exists, -M must be used to force the rename +to happen. + With a `-d` or `-D` option, `` will be deleted. You may specify more than one branch for deletion. If the branch currently has a ref log then the ref log will also be deleted. @@ -46,6 +53,12 @@ OPTIONS Force the creation of a new branch even if it means deleting a branch that already exists with the same name. +-m:: + Move/rename a branch and the corresponding reflog. + +-M:: + Move/rename a branch even if the new branchname already exists. + -r:: List the remote-tracking branches. @@ -53,7 +66,7 @@ OPTIONS List both remote-tracking branches and local branches. -v:: - Show sha1 and subject message for each head. + Show sha1 and commit subjectline for each head. --abbrev=:: Alter minimum display length for sha1 in output listing, @@ -70,6 +83,12 @@ OPTIONS be given as a branch name, a commit-id, or a tag. If this option is omitted, the current branch is assumed. +:: + The name of an existing branch to rename. + +:: + The new name for an existing branch. The same restrictions as for + applies. Examples diff --git a/builtin-branch.c b/builtin-branch.c index 3d5cb0e..1536826 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -11,7 +11,7 @@ #include "builtin.h" static const char builtin_branch_usage[] = -"git-branch (-d | -D) | [-l] [-f] [] | [-r | -a] [-v] [--abbrev=] "; + "git-branch (-d | -D) | [-l] [-f] [] | (-m | -M) [] | [-r | -a] [-v [--abbrev=]]"; static const char *head; @@ -245,9 +245,37 @@ static void create_branch(const char *name, const char *start, die("Failed to write ref: %s.", strerror(errno)); } +static void rename_branch(const char *oldname, const char *newname, int force) +{ + char oldref[PATH_MAX], newref[PATH_MAX]; + unsigned char sha1[20]; + + if (snprintf(oldref, sizeof(oldref), "refs/heads/%s", oldname) > sizeof(oldref)) + die("Old branchname too long"); + + if (check_ref_format(oldref)) + die("Invalid branch name: %s", oldref); + + if (snprintf(newref, sizeof(newref), "refs/heads/%s", newname) > sizeof(newref)) + die("New branchname too long"); + + if (check_ref_format(newref)) + die("Invalid branch name: %s", newref); + + if (resolve_ref(newref, sha1, 1, NULL) && !force) + die("A branch named '%s' already exists.", newname); + + if (rename_ref(oldref, newref)) + die("Branch rename failed"); + + if (!strcmp(oldname, head) && create_symref("HEAD", newref)) + die("Branch renamed to %s, but HEAD is not updated!", newname); +} + int cmd_branch(int argc, const char **argv, const char *prefix) { int delete = 0, force_delete = 0, force_create = 0; + int rename = 0, force_rename = 0; int verbose = 0, abbrev = DEFAULT_ABBREV; int reflog = 0; int kinds = REF_LOCAL_BRANCH; @@ -277,6 +305,15 @@ int cmd_branch(int argc, const char **argv, const char *prefix) force_create = 1; continue; } + if (!strcmp(arg, "-m")) { + rename = 1; + continue; + } + if (!strcmp(arg, "-M")) { + rename = 1; + force_rename = 1; + continue; + } if (!strcmp(arg, "-r")) { kinds = REF_REMOTE_BRANCH; continue; @@ -300,6 +337,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix) usage(builtin_branch_usage); } + if ((delete && rename) || (delete && force_create) || + (rename && force_create)) + usage(builtin_branch_usage); + head = xstrdup(resolve_ref("HEAD", head_sha1, 0, NULL)); if (!head) die("Failed to resolve HEAD as a valid ref."); @@ -311,6 +352,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix) delete_branches(argc - i, argv + i, force_delete); else if (i == argc) print_ref_list(kinds, verbose, abbrev); + else if (rename && (i == argc - 1)) + rename_branch(head, argv[i], force_rename); + else if (rename && (i == argc - 2)) + rename_branch(argv[i], argv[i + 1], force_rename); else if (i == argc - 1) create_branch(argv[i], head, force_create, reflog); else if (i == argc - 2) diff --git a/refs.c b/refs.c index 96ea8b6..cdedb45 100644 --- a/refs.c +++ b/refs.c @@ -610,6 +610,29 @@ static int remove_empty_directories(char *file) return remove_empty_dir_recursive(path, len); } +static int is_refname_available(const char *ref, const char *oldref, + struct ref_list *list, int quiet) +{ + int namlen = strlen(ref); /* e.g. 'foo/bar' */ + while (list) { + /* list->name could be 'foo' or 'foo/bar/baz' */ + if (!oldref || strcmp(oldref, list->name)) { + int len = strlen(list->name); + int cmplen = (namlen < len) ? namlen : len; + const char *lead = (namlen < len) ? list->name : ref; + if (!strncmp(ref, list->name, cmplen) && + lead[cmplen] == '/') { + if (!quiet) + error("'%s' exists; cannot create '%s'", + list->name, ref); + return 0; + } + } + list = list->next; + } + return 1; +} + static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int *flag) { char *ref_file; @@ -643,29 +666,14 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char orig_ref, strerror(errno)); goto error_return; } - if (is_null_sha1(lock->old_sha1)) { - /* The ref did not exist and we are creating it. - * Make sure there is no existing ref that is packed - * whose name begins with our refname, nor a ref whose - * name is a proper prefix of our refname. - */ - int namlen = strlen(ref); /* e.g. 'foo/bar' */ - struct ref_list *list = get_packed_refs(); - while (list) { - /* list->name could be 'foo' or 'foo/bar/baz' */ - int len = strlen(list->name); - int cmplen = (namlen < len) ? namlen : len; - const char *lead = (namlen < len) ? list->name : ref; - - if (!strncmp(ref, list->name, cmplen) && - lead[cmplen] == '/') { - error("'%s' exists; cannot create '%s'", - list->name, ref); - goto error_return; - } - list = list->next; - } - } + /* When the ref did not exist and we are creating it, + * make sure there is no existing ref that is packed + * whose name begins with our refname, nor a ref whose + * name is a proper prefix of our refname. + */ + if (is_null_sha1(lock->old_sha1) && + !is_refname_available(ref, NULL, get_packed_refs(), 0)) + goto error_return; lock->lk = xcalloc(1, sizeof(struct lock_file)); @@ -776,6 +784,121 @@ int delete_ref(const char *refname, unsigned char *sha1) return ret; } +int rename_ref(const char *oldref, const char *newref) +{ + static const char renamed_ref[] = "RENAMED-REF"; + unsigned char sha1[20], orig_sha1[20]; + int flag = 0, logmoved = 0; + struct ref_lock *lock; + char msg[PATH_MAX*2 + 100]; + struct stat loginfo; + int log = !stat(git_path("logs/%s", oldref), &loginfo); + + if (S_ISLNK(loginfo.st_mode)) + return error("reflog for %s is a symlink", oldref); + + if (!resolve_ref(oldref, orig_sha1, 1, &flag)) + return error("refname %s not found", oldref); + + if (!is_refname_available(newref, oldref, get_packed_refs(), 0)) + return 1; + + if (!is_refname_available(newref, oldref, get_loose_refs(), 0)) + return 1; + + if (snprintf(msg, sizeof(msg), "renamed %s to %s", oldref, newref) > sizeof(msg)) + return error("Refnames to long"); + + lock = lock_ref_sha1_basic(renamed_ref, NULL, NULL); + if (!lock) + return error("unable to lock %s", renamed_ref); + lock->force_write = 1; + if (write_ref_sha1(lock, orig_sha1, msg)) + return error("unable to save current sha1 in %s", renamed_ref); + + if (log && rename(git_path("logs/%s", oldref), git_path("tmp-renamed-log"))) + return error("unable to move logfile logs/%s to tmp-renamed-log: %s", + oldref, strerror(errno)); + + if (delete_ref(oldref, orig_sha1)) { + error("unable to delete old %s", oldref); + goto rollback; + } + + if (resolve_ref(newref, sha1, 1, &flag) && delete_ref(newref, sha1)) { + if (errno==EISDIR) { + if (remove_empty_directories(git_path("%s", newref))) { + error("Directory not empty: %s", newref); + goto rollback; + } + } else { + error("unable to delete existing %s", newref); + goto rollback; + } + } + + if (log && safe_create_leading_directories(git_path("logs/%s", newref))) { + error("unable to create directory for %s", newref); + goto rollback; + } + + retry: + if (log && rename(git_path("tmp-renamed-log"), git_path("logs/%s", newref))) { + if (errno==EISDIR) { + if (remove_empty_directories(git_path("logs/%s", newref))) { + error("Directory not empty: logs/%s", newref); + goto rollback; + } + goto retry; + } else { + error("unable to move logfile tmp-renamed-log to logs/%s: %s", + newref, strerror(errno)); + goto rollback; + } + } + logmoved = log; + + lock = lock_ref_sha1_basic(newref, NULL, NULL); + if (!lock) { + error("unable to lock %s for update", newref); + goto rollback; + } + + lock->force_write = 1; + hashcpy(lock->old_sha1, orig_sha1); + if (write_ref_sha1(lock, orig_sha1, msg)) { + error("unable to write current sha1 into %s", newref); + goto rollback; + } + + return 0; + + rollback: + lock = lock_ref_sha1_basic(oldref, NULL, NULL); + if (!lock) { + error("unable to lock %s for rollback", oldref); + goto rollbacklog; + } + + lock->force_write = 1; + flag = log_all_ref_updates; + log_all_ref_updates = 0; + if (write_ref_sha1(lock, orig_sha1, NULL)) + error("unable to write current sha1 into %s", oldref); + log_all_ref_updates = flag; + + rollbacklog: + if (logmoved && rename(git_path("logs/%s", newref), git_path("logs/%s", oldref))) + error("unable to restore logfile %s from %s: %s", + oldref, newref, strerror(errno)); + if (!logmoved && log && + rename(git_path("tmp-renamed-log"), git_path("logs/%s", oldref))) + error("unable to restore logfile %s from tmp-renamed-log: %s", + oldref, strerror(errno)); + + return 1; +} + void unlock_ref(struct ref_lock *lock) { if (lock->lock_fd >= 0) { diff --git a/refs.h b/refs.h index cd1e1d6..ce73d5c 100644 --- a/refs.h +++ b/refs.h @@ -47,4 +47,7 @@ extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned /** Returns 0 if target has the right format for a ref. **/ extern int check_ref_format(const char *target); +/** rename ref, return 0 on success **/ +extern int rename_ref(const char *oldref, const char *newref); + #endif /* REFS_H */ diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index acb54b6..afaa853 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -70,4 +70,38 @@ test_expect_success \ git-branch -d l/m && git-branch l' +test_expect_success \ + 'git branch -m m m/m should work' \ + 'git-branch -l m && + git-branch -m m m/m && + test -f .git/logs/refs/heads/m/m' + +test_expect_success \ + 'git branch -m n/n n should work' \ + 'git-branch -l n/n && + git-branch -m n/n n + test -f .git/logs/refs/heads/n' + +test_expect_failure \ + 'git branch -m o/o o should fail when o/p exists' \ + 'git-branch o/o && + git-branch o/p && + git-branch -m o/o o' + +test_expect_failure \ + 'git branch -m q r/q should fail when r exists' \ + 'git-branch q && + git-branch r && + git-branch -m q r/q' + +test_expect_success \ + 'git branch -m s/s s should work when s/t is deleted' \ + 'git-branch -l s/s && + test -f .git/logs/refs/heads/s/s && + git-branch -l s/t && + test -f .git/logs/refs/heads/s/t && + git-branch -d s/t && + git-branch -m s/s s && + test -f .git/logs/refs/heads/s' + test_done -- cgit v0.10.2-6-g49f6 From 16c2bfbb449a90db00a46984d7dd7f735caa1d56 Mon Sep 17 00:00:00 2001 From: Lars Hjemli Date: Wed, 29 Nov 2006 21:44:56 +0100 Subject: rename_ref: use lstat(2) when testing for symlink The current check for symlinked reflogs was based on stat(2), which is utterly embarrassing. Fix it, and add a matching testcase. Signed-off-by: Lars Hjemli Signed-off-by: Junio C Hamano diff --git a/refs.c b/refs.c index cdedb45..c23561e 100644 --- a/refs.c +++ b/refs.c @@ -792,7 +792,7 @@ int rename_ref(const char *oldref, const char *newref) struct ref_lock *lock; char msg[PATH_MAX*2 + 100]; struct stat loginfo; - int log = !stat(git_path("logs/%s", oldref), &loginfo); + int log = !lstat(git_path("logs/%s", oldref), &loginfo); if (S_ISLNK(loginfo.st_mode)) return error("reflog for %s is a symlink", oldref); diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index afaa853..5782c30 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -104,4 +104,11 @@ test_expect_success \ git-branch -m s/s s && test -f .git/logs/refs/heads/s' +test_expect_failure \ + 'git-branch -m u v should fail when the reflog for u is a symlink' \ + 'git-branch -l u && + mv .git/logs/refs/heads/u real-u && + ln -s real-u .git/logs/refs/heads/u && + git-branch -m u v' + test_done -- cgit v0.10.2-6-g49f6 From 678d0f4cbfa7a3b529c6e894f2977bef6a2d3e4c Mon Sep 17 00:00:00 2001 From: Lars Hjemli Date: Thu, 30 Nov 2006 03:16:56 +0100 Subject: git-branch: let caller specify logmsg This changes the signature of rename_ref() in refs.[hc] to include a logmessage for the reflogs. Also, builtin-branch.c is modified to provide a proper logmessage + call setup_ident() before any logmessages are written. Signed-off-by: Lars Hjemli Signed-off-by: Junio C Hamano diff --git a/builtin-branch.c b/builtin-branch.c index 1536826..3fc6f84 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -247,7 +247,7 @@ static void create_branch(const char *name, const char *start, static void rename_branch(const char *oldname, const char *newname, int force) { - char oldref[PATH_MAX], newref[PATH_MAX]; + char oldref[PATH_MAX], newref[PATH_MAX], logmsg[PATH_MAX*2 + 100]; unsigned char sha1[20]; if (snprintf(oldref, sizeof(oldref), "refs/heads/%s", oldname) > sizeof(oldref)) @@ -265,7 +265,10 @@ static void rename_branch(const char *oldname, const char *newname, int force) if (resolve_ref(newref, sha1, 1, NULL) && !force) die("A branch named '%s' already exists.", newname); - if (rename_ref(oldref, newref)) + snprintf(logmsg, sizeof(logmsg), "Branch: renamed %s to %s", + oldref, newref); + + if (rename_ref(oldref, newref, logmsg)) die("Branch rename failed"); if (!strcmp(oldname, head) && create_symref("HEAD", newref)) @@ -281,6 +284,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) int kinds = REF_LOCAL_BRANCH; int i; + setup_ident(); git_config(git_default_config); for (i = 1; i < argc; i++) { diff --git a/refs.c b/refs.c index c23561e..2ac8273 100644 --- a/refs.c +++ b/refs.c @@ -784,13 +784,12 @@ int delete_ref(const char *refname, unsigned char *sha1) return ret; } -int rename_ref(const char *oldref, const char *newref) +int rename_ref(const char *oldref, const char *newref, const char *logmsg) { static const char renamed_ref[] = "RENAMED-REF"; unsigned char sha1[20], orig_sha1[20]; int flag = 0, logmoved = 0; struct ref_lock *lock; - char msg[PATH_MAX*2 + 100]; struct stat loginfo; int log = !lstat(git_path("logs/%s", oldref), &loginfo); @@ -806,14 +805,11 @@ int rename_ref(const char *oldref, const char *newref) if (!is_refname_available(newref, oldref, get_loose_refs(), 0)) return 1; - if (snprintf(msg, sizeof(msg), "renamed %s to %s", oldref, newref) > sizeof(msg)) - return error("Refnames to long"); - lock = lock_ref_sha1_basic(renamed_ref, NULL, NULL); if (!lock) return error("unable to lock %s", renamed_ref); lock->force_write = 1; - if (write_ref_sha1(lock, orig_sha1, msg)) + if (write_ref_sha1(lock, orig_sha1, logmsg)) return error("unable to save current sha1 in %s", renamed_ref); if (log && rename(git_path("logs/%s", oldref), git_path("tmp-renamed-log"))) @@ -866,7 +862,7 @@ int rename_ref(const char *oldref, const char *newref) lock->force_write = 1; hashcpy(lock->old_sha1, orig_sha1); - if (write_ref_sha1(lock, orig_sha1, msg)) { + if (write_ref_sha1(lock, orig_sha1, logmsg)) { error("unable to write current sha1 into %s", newref); goto rollback; } diff --git a/refs.h b/refs.h index ce73d5c..51aab1e 100644 --- a/refs.h +++ b/refs.h @@ -48,6 +48,6 @@ extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned extern int check_ref_format(const char *target); /** rename ref, return 0 on success **/ -extern int rename_ref(const char *oldref, const char *newref); +extern int rename_ref(const char *oldref, const char *newref, const char *logmsg); #endif /* REFS_H */ -- cgit v0.10.2-6-g49f6 From 3a9f1a55eec9cc508abccda6a3fee795b812d66d Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 6 Dec 2006 13:27:40 +0100 Subject: cvs-migration document: make the need for "push" more obvious It really is an important concept to grasp for people coming from CVS. Even if it is briefly mentioned, it is not obvious enough to sink in. [jc: with wording updates from J. Bruce Fields] Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/Documentation/cvs-migration.txt b/Documentation/cvs-migration.txt index 6812683..9c2a395 100644 --- a/Documentation/cvs-migration.txt +++ b/Documentation/cvs-migration.txt @@ -24,6 +24,11 @@ First, note some ways that git differs from CVS: single shared repository which people can synchronize with; see below for details. + * Since every working tree contains a repository, a commit in your + private repository will not publish your changes; it will only create + a revision. You have to "push" your changes to a public repository to + make them visible to others. + Importing a CVS archive ----------------------- -- cgit v0.10.2-6-g49f6 From 4003a58e415e5f51a3becac0079505b72299a7bc Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Wed, 6 Dec 2006 12:19:50 -0500 Subject: cvs-migration: improved section titles, better push/commit explanation Rename the section titles to make the "how-to" content of the section obvious. Also clarify that changes have to be commited before they can be pushed. Signed-off-by: Junio C Hamano diff --git a/Documentation/cvs-migration.txt b/Documentation/cvs-migration.txt index 9c2a395..a436180 100644 --- a/Documentation/cvs-migration.txt +++ b/Documentation/cvs-migration.txt @@ -81,8 +81,8 @@ variants of this model. With a small group, developers may just pull changes from each other's repositories without the need for a central maintainer. -Emulating the CVS Development Model ------------------------------------ +Creating a Shared Repository +---------------------------- Start with an ordinary git working directory containing the project, and remove the checked-out files, keeping just the bare .git directory: @@ -110,7 +110,10 @@ $ GIT_DIR=repo.git git repo-config core.sharedrepository true Make sure committers have a umask of at most 027, so that the directories they create are writable and searchable by other group members. -Suppose this repository is now set up in /pub/repo.git on the host +Performing Development on a Shared Repository +--------------------------------------------- + +Suppose a repository is now set up in /pub/repo.git on the host foo.com. Then as an individual committer you can clone the shared repository: @@ -139,15 +142,17 @@ Pull: master:origin ------------ ================================ -You can update the shared repository with your changes using: +You can update the shared repository with your changes by first commiting +your changes, and then using: ------------------------------------------------ $ git push origin master ------------------------------------------------ -If someone else has updated the repository more recently, `git push`, like -`cvs commit`, will complain, in which case you must pull any changes -before attempting the push again. +to "push" those commits to the shared repository. If someone else has +updated the repository more recently, `git push`, like `cvs commit`, will +complain, in which case you must pull any changes before attempting the +push again. In the `git push` command above we specify the name of the remote branch to update (`master`). If we leave that out, `git push` tries to update -- cgit v0.10.2-6-g49f6 From ba1f5f353775ddbf97bc0d543888783630693023 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 6 Dec 2006 16:26:06 +0100 Subject: Add builtin merge-file, a minimal replacement for RCS merge merge-file has the same syntax as RCS merge, but supports only the "-L" option. For good measure, a test is added, which is quite minimal, though. [jc: further fix for compliation errors included.] Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 23bbb90..79cb91f 100644 --- a/Makefile +++ b/Makefile @@ -275,6 +275,7 @@ BUILTIN_OBJS = \ builtin-ls-tree.o \ builtin-mailinfo.o \ builtin-mailsplit.o \ + builtin-merge-file.o \ builtin-mv.o \ builtin-name-rev.o \ builtin-pack-objects.o \ diff --git a/builtin-merge-file.c b/builtin-merge-file.c new file mode 100644 index 0000000..41fb92e --- /dev/null +++ b/builtin-merge-file.c @@ -0,0 +1,72 @@ +#include "cache.h" +#include "xdiff/xdiff.h" + +static const char merge_file_usage[] = +"git merge-file [-L name1 [-L orig [-L name2]]] file1 orig_file file2"; + +static int read_file(mmfile_t *ptr, const char *filename) +{ + struct stat st; + FILE *f; + + if (stat(filename, &st)) + return error("Could not stat %s", filename); + if ((f = fopen(filename, "rb")) == NULL) + return error("Could not open %s", filename); + ptr->ptr = xmalloc(st.st_size); + if (fread(ptr->ptr, st.st_size, 1, f) != 1) + return error("Could not read %s", filename); + fclose(f); + ptr->size = st.st_size; + return 0; +} + +int cmd_merge_file(int argc, char **argv, char **envp) +{ + char *names[3]; + mmfile_t mmfs[3]; + mmbuffer_t result = {NULL, 0}; + xpparam_t xpp = {XDF_NEED_MINIMAL}; + int ret = 0, i = 0; + + while (argc > 4) { + if (!strcmp(argv[1], "-L")) { + names[i++] = argv[2]; + argc -= 2; + argv += 2; + continue; + } + usage(merge_file_usage); + } + + if (argc != 4) + usage(merge_file_usage); + + for (; i < 3; i++) + names[i] = argv[i + 1]; + + for (i = 0; i < 3; i++) + if (read_file(mmfs + i, argv[i + 1])) + return -1; + + ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2], + &xpp, XDL_MERGE_ZEALOUS, &result); + + for (i = 0; i < 3; i++) + free(mmfs[i].ptr); + + if (ret >= 0) { + char *filename = argv[1]; + FILE *f = fopen(filename, "wb"); + + if (!f) + ret = error("Could not open %s for writing", filename); + else if (fwrite(result.ptr, result.size, 1, f) != 1) + ret = error("Could not write to %s", filename); + else if (fclose(f)) + ret = error("Could not close %s", filename); + free(result.ptr); + } + + return ret; +} diff --git a/builtin.h b/builtin.h index b5116f3..08519e7 100644 --- a/builtin.h +++ b/builtin.h @@ -42,6 +42,7 @@ extern int cmd_ls_files(int argc, const char **argv, const char *prefix); extern int cmd_ls_tree(int argc, const char **argv, const char *prefix); extern int cmd_mailinfo(int argc, const char **argv, const char *prefix); extern int cmd_mailsplit(int argc, const char **argv, const char *prefix); +extern int cmd_merge_file(int argc, const char **argv, const char *prefix); extern int cmd_mv(int argc, const char **argv, const char *prefix); extern int cmd_name_rev(int argc, const char **argv, const char *prefix); extern int cmd_pack_objects(int argc, const char **argv, const char *prefix); diff --git a/git.c b/git.c index 357330e..016ee8a 100644 --- a/git.c +++ b/git.c @@ -247,6 +247,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "ls-tree", cmd_ls_tree, RUN_SETUP }, { "mailinfo", cmd_mailinfo }, { "mailsplit", cmd_mailsplit }, + { "merge-file", cmd_merge_file }, { "mv", cmd_mv, RUN_SETUP }, { "name-rev", cmd_name_rev, RUN_SETUP }, { "pack-objects", cmd_pack_objects, RUN_SETUP }, diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh new file mode 100644 index 0000000..5d9b6f3 --- /dev/null +++ b/t/t6023-merge-file.sh @@ -0,0 +1,116 @@ +#!/bin/sh + +test_description='RCS merge replacement: merge-file' +. ./test-lib.sh + +cat > orig.txt << EOF +Dominus regit me, +et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +EOF + +cat > new1.txt << EOF +Dominus regit me, +et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +Nam et si ambulavero in medio umbrae mortis, +non timebo mala, quoniam tu mecum es: +virga tua et baculus tuus ipsa me consolata sunt. +EOF + +cat > new2.txt << EOF +Dominus regit me, et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +EOF + +cat > new3.txt << EOF +DOMINUS regit me, +et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +EOF + +cat > new4.txt << EOF +Dominus regit me, et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +EOF +echo -n "propter nomen suum." >> new4.txt + +cp new1.txt test.txt +test_expect_success "merge without conflict" \ + "git-merge-file test.txt orig.txt new2.txt" + +cp new1.txt test2.txt +test_expect_success "merge without conflict (missing LF at EOF)" \ + "git-merge-file test2.txt orig.txt new2.txt" + +test_expect_success "merge result added missing LF" \ + "diff -u test.txt test2.txt" + +cp test.txt backup.txt +test_expect_failure "merge with conflicts" \ + "git-merge-file test.txt orig.txt new3.txt" + +cat > expect.txt << EOF +<<<<<<< test.txt +Dominus regit me, et nihil mihi deerit. +======= +DOMINUS regit me, +et nihil mihi deerit. +>>>>>>> new3.txt +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +Nam et si ambulavero in medio umbrae mortis, +non timebo mala, quoniam tu mecum es: +virga tua et baculus tuus ipsa me consolata sunt. +EOF + +test_expect_success "expected conflict markers" "diff -u test.txt expect.txt" + +cp backup.txt test.txt +test_expect_failure "merge with conflicts, using -L" \ + "git-merge-file -L 1 -L 2 test.txt orig.txt new3.txt" + +cat > expect.txt << EOF +<<<<<<< 1 +Dominus regit me, et nihil mihi deerit. +======= +DOMINUS regit me, +et nihil mihi deerit. +>>>>>>> new3.txt +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +Nam et si ambulavero in medio umbrae mortis, +non timebo mala, quoniam tu mecum es: +virga tua et baculus tuus ipsa me consolata sunt. +EOF + +test_expect_success "expected conflict markers, with -L" \ + "diff -u test.txt expect.txt" + +test_done + -- cgit v0.10.2-6-g49f6 From fbe0b24ca53111208b8ecd920cc112d0be13f93c Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 6 Dec 2006 16:45:42 +0100 Subject: merge-file: support -p and -q; fix compile warnings Now merge-file also understands --stdout and --quiet options. While at it, two compile warnings were fixed. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/builtin-merge-file.c b/builtin-merge-file.c index 41fb92e..6c4c3a3 100644 --- a/builtin-merge-file.c +++ b/builtin-merge-file.c @@ -2,7 +2,7 @@ #include "xdiff/xdiff.h" static const char merge_file_usage[] = -"git merge-file [-L name1 [-L orig [-L name2]]] file1 orig_file file2"; +"git merge-file [-p | --stdout] [-q | --quiet] [-L name1 [-L orig [-L name2]]] file1 orig_file file2"; static int read_file(mmfile_t *ptr, const char *filename) { @@ -27,16 +27,23 @@ int cmd_merge_file(int argc, char **argv, char **envp) mmfile_t mmfs[3]; mmbuffer_t result = {NULL, 0}; xpparam_t xpp = {XDF_NEED_MINIMAL}; - int ret = 0, i = 0; + int ret = 0, i = 0, to_stdout = 0; while (argc > 4) { - if (!strcmp(argv[1], "-L")) { + if (!strcmp(argv[1], "-L") && i < 3) { names[i++] = argv[2]; - argc -= 2; - argv += 2; - continue; - } - usage(merge_file_usage); + argc--; + argv++; + } else if (!strcmp(argv[1], "-p") || + !strcmp(argv[1], "--stdout")) + to_stdout = 1; + else if (!strcmp(argv[1], "-q") || + !strcmp(argv[1], "--quiet")) + freopen("/dev/null", "w", stderr); + else + usage(merge_file_usage); + argc--; + argv++; } if (argc != 4) @@ -57,7 +64,7 @@ int cmd_merge_file(int argc, char **argv, char **envp) if (ret >= 0) { char *filename = argv[1]; - FILE *f = fopen(filename, "wb"); + FILE *f = to_stdout ? stdout : fopen(filename, "wb"); if (!f) ret = error("Could not open %s for writing", filename); -- cgit v0.10.2-6-g49f6 From 49ed2bc4660c7cd0592cf21cc514080574d06320 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 4 Dec 2006 19:44:40 -0800 Subject: git-reset to remove "$GIT_DIR/MERGE_MSG" An earlier commit a9cb3c6e changed git-commit to use the contents of MERGE_MSG even when we do not have MERGE_HEAD (the rationale is in its log message). However, the change tricks the following sequence to include a merge message in a completely unrelated commit: $ git pull somewhere : oops, the conflicts are too much. forget it. $ git reset --hard : work work work $ git commit To fix this confusion, this patch makes "git reset" to remove the leftover MERGE_MSG that was prepared when the user abandoned the merge. Signed-off-by: Junio C Hamano Acked-by: Luben Tuikov Date: Wed, 6 Dec 2006 10:52:04 -0800 Subject: git-merge: squelch needless error message. While deciding if the new style command line argument is a tag or a branch, we checked it with "git show-ref -s --verify" to see if results in an error, but when it is not a branch, the check leaked the error message out, which was not needed to be shown to the end user. Signed-off-by: Junio C Hamano diff --git a/git-merge.sh b/git-merge.sh index 272f004..efdbabf 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -189,7 +189,7 @@ else merge_name=$(for remote do rh=$(git-rev-parse --verify "$remote"^0 2>/dev/null) && - bh=$(git show-ref -s --verify "refs/heads/$remote") && + bh=$(git show-ref -s --verify "refs/heads/$remote" 2>/dev/null) && if test "$rh" = "$bh" then echo "$rh branch '$remote' of ." -- cgit v0.10.2-6-g49f6 From 5a4cf3346d6c37007a7f5f94697868a5b2f2fa29 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 4 Dec 2006 23:47:22 +0100 Subject: gitweb: Allow PNG, GIF, JPEG images to be displayed in "blob" view Allow images in one of web formats (PNG, GIF, JPEG) - actually files with mimetype of image/png, image/git, image/jpeg - to be displayed in "blob" view using element, instead of using "blob_plain" view for them, like for all other files except also text/* mimetype files. This makes possible to easily go to file history, to HEAD version of the file, to appropriate commit etc; all of those are not available in "blob_plain" (raw) view. Only text files can have "blame" view link in the formats part of navbar. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index ffe8ce1..61e2ab2 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3229,10 +3229,13 @@ sub git_blob { open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash or die_error(undef, "Couldn't cat $file_name, $hash"); my $mimetype = blob_mimetype($fd, $file_name); - if ($mimetype !~ m/^text\//) { + if ($mimetype !~ m!^(?:text/|image/(?:gif|png|jpeg)$)!) { close $fd; return git_blob_plain($mimetype); } + # we can have blame only for text/* mimetype + $have_blame &&= ($mimetype =~ m!^text/!); + git_header_html(undef, $expires); my $formats_nav = ''; if (defined $hash_base && (my %co = parse_commit($hash_base))) { @@ -3269,13 +3272,24 @@ sub git_blob { } git_print_page_path($file_name, "blob", $hash_base); print "
\n"; - my $nr; - while (my $line = <$fd>) { - chomp $line; - $nr++; - $line = untabify($line); - printf "
%4i %s
\n", - $nr, $nr, $nr, esc_html($line, -nbsp=>1); + if ($mimetype =~ m!^text/!) { + my $nr; + while (my $line = <$fd>) { + chomp $line; + $nr++; + $line = untabify($line); + printf "
%4i %s
\n", + $nr, $nr, $nr, esc_html($line, -nbsp=>1); + } + } elsif ($mimetype =~ m!^image/!) { + print qq!$file_name$hash, + hash_base=>$hash_base, file_name=>$file_name) . + qq!" />\n!; } close $fd or print "Reading blob failed.\n"; -- cgit v0.10.2-6-g49f6 From ebdf7b952215946eff863e4da28f924178acea4f Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 4 Dec 2006 00:51:16 -0800 Subject: git-svn: avoid network timeouts for long-running fetches Long-running fetches run inside children to avoid memory leaks. When we refork, the connection in the parent can be idle for a long time; attempting to reuse it in the next child can result in timeouts. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index d0bd0bd..747daf0 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -459,6 +459,7 @@ sub fetch_lib { $min = $max + 1; $max += $inc; $max = $head if ($max > $head); + $SVN = libsvn_connect($SVN_URL); } restore_index($index); return { revision => $last_rev, commit => $last_commit }; -- cgit v0.10.2-6-g49f6 From de51faf3888505fa3d661d4c35f32ecaf9fa1087 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 6 Dec 2006 11:22:55 -0800 Subject: git-merge: fix "fix confusion between tag and branch" for real An earlier commit 3683dc5a broke the merge message generation with a careless use of && where it was not needed, breaking the merge message for cases where non branches are given. Signed-off-by: Junio C Hamano diff --git a/git-merge.sh b/git-merge.sh index efdbabf..a948878 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -188,8 +188,9 @@ else # in this loop. merge_name=$(for remote do - rh=$(git-rev-parse --verify "$remote"^0 2>/dev/null) && - bh=$(git show-ref -s --verify "refs/heads/$remote" 2>/dev/null) && + rh=$(git-rev-parse --verify "$remote"^0 2>/dev/null) || + continue ;# not something we can merge + bh=$(git show-ref -s --verify "refs/heads/$remote" 2>/dev/null) if test "$rh" = "$bh" then echo "$rh branch '$remote' of ." -- cgit v0.10.2-6-g49f6 From 955289bf92f0513377763c9aacfe426d5151b05a Mon Sep 17 00:00:00 2001 From: Andy Parkins Date: Wed, 6 Dec 2006 12:07:23 +0000 Subject: Explicitly add the default "git pull" behaviour to .git/config on clone Without any specification in the .git/config file, git-pull will execute "git-pull origin"; which in turn defaults to pull from the first "pull" definition for the remote, "origin". This is a difficult set of defaults to track for a new user, and it's difficult to see what tells git to do this (especially when it is actually hard-coded behaviour). To ameliorate this slightly, this patch explicitly specifies the default behaviour during a clone using the "branch" section of the config. For example, a clone of a typical repository would create a .git/config containing: [remote "origin"] url = proto://host/repo.git fetch = refs/heads/master:refs/remotes/origin/master [branch "master"] remote = origin merge = refs/heads/master The [branch "master"] section is such that there is no change to the functionality of git-pull, but that functionality is now explicitly documented. Signed-off-by: Andy Parkins diff --git a/git-clone.sh b/git-clone.sh index 0ace989..1f5d07a 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -400,7 +400,9 @@ then rm -f "refs/remotes/$origin/HEAD" git-symbolic-ref "refs/remotes/$origin/HEAD" \ "refs/remotes/$origin/$head_points_at" - esac + esac && + git-repo-config branch."$head_points_at".remote "$origin" && + git-repo-config branch."$head_points_at".merge "refs/heads/$head_points_at" esac case "$no_checkout" in -- cgit v0.10.2-6-g49f6 From cd976f5c52694acb4b23c3f2425ed4f0a47ec799 Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Wed, 6 Dec 2006 23:18:05 -0500 Subject: Documentation: reorganize cvs-migration.txt Modify cvs-migration.txt so it explains first how to develop against a shared repository, then how to set up a shared repository, then how to import a repository from cvs. Though this seems chronologically backwards, it's still readable in this order, and it puts the more commonly needed material closer to the front. Remove the annotate/pickaxe section; perhaps it can find a place elsewhere in the future. Remove most of the "why git is better than cvs" stuff from the introduction. Add some minor clarifications, including two that have come up several times on the mailing list: 1. Recommend committing any changes before running pull. 2. Note that changes must be commited before they can be pushed. Update the clone discussion to reflect the new --use-separate-remotes default, and add a brief mention of git-cvsserver. Signed-off-by: J. Bruce Fields Signed-off-by: Junio C Hamano diff --git a/Documentation/cvs-migration.txt b/Documentation/cvs-migration.txt index a436180..47846bd 100644 --- a/Documentation/cvs-migration.txt +++ b/Documentation/cvs-migration.txt @@ -1,121 +1,21 @@ git for CVS users ================= -So you're a CVS user. That's OK, it's a treatable condition. The job of -this document is to put you on the road to recovery, by helping you -convert an existing cvs repository to git, and by showing you how to use a -git repository in a cvs-like fashion. +Git differs from CVS in that every working tree contains a repository with +a full copy of the project history, and no repository is inherently more +important than any other. However, you can emulate the CVS model by +designating a single shared repository which people can synchronize with; +this document explains how to do that. Some basic familiarity with git is required. This link:tutorial.html[tutorial introduction to git] should be sufficient. -First, note some ways that git differs from CVS: +Developing against a shared repository +-------------------------------------- - * Commits are atomic and project-wide, not per-file as in CVS. - - * Offline work is supported: you can make multiple commits locally, - then submit them when you're ready. - - * Branching is fast and easy. - - * Every working tree contains a repository with a full copy of the - project history, and no repository is inherently more important than - any other. However, you can emulate the CVS model by designating a - single shared repository which people can synchronize with; see below - for details. - - * Since every working tree contains a repository, a commit in your - private repository will not publish your changes; it will only create - a revision. You have to "push" your changes to a public repository to - make them visible to others. - -Importing a CVS archive ------------------------ - -First, install version 2.1 or higher of cvsps from -link:http://www.cobite.com/cvsps/[http://www.cobite.com/cvsps/] and make -sure it is in your path. The magic command line is then - -------------------------------------------- -$ git cvsimport -v -d -C -------------------------------------------- - -This puts a git archive of the named CVS module in the directory -, which will be created if necessary. The -v option makes -the conversion script very chatty. - -The import checks out from CVS every revision of every file. Reportedly -cvsimport can average some twenty revisions per second, so for a -medium-sized project this should not take more than a couple of minutes. -Larger projects or remote repositories may take longer. - -The main trunk is stored in the git branch named `origin`, and additional -CVS branches are stored in git branches with the same names. The most -recent version of the main trunk is also left checked out on the `master` -branch, so you can start adding your own changes right away. - -The import is incremental, so if you call it again next month it will -fetch any CVS updates that have been made in the meantime. For this to -work, you must not modify the imported branches; instead, create new -branches for your own changes, and merge in the imported branches as -necessary. - -Development Models ------------------- - -CVS users are accustomed to giving a group of developers commit access to -a common repository. In the next section we'll explain how to do this -with git. However, the distributed nature of git allows other development -models, and you may want to first consider whether one of them might be a -better fit for your project. - -For example, you can choose a single person to maintain the project's -primary public repository. Other developers then clone this repository -and each work in their own clone. When they have a series of changes that -they're happy with, they ask the maintainer to pull from the branch -containing the changes. The maintainer reviews their changes and pulls -them into the primary repository, which other developers pull from as -necessary to stay coordinated. The Linux kernel and other projects use -variants of this model. - -With a small group, developers may just pull changes from each other's -repositories without the need for a central maintainer. - -Creating a Shared Repository ----------------------------- - -Start with an ordinary git working directory containing the project, and -remove the checked-out files, keeping just the bare .git directory: - ------------------------------------------------- -$ mv project/.git /pub/repo.git -$ rm -r project/ ------------------------------------------------- - -Next, give every team member read/write access to this repository. One -easy way to do this is to give all the team members ssh access to the -machine where the repository is hosted. If you don't want to give them a -full shell on the machine, there is a restricted shell which only allows -users to do git pushes and pulls; see gitlink:git-shell[1]. - -Put all the committers in the same group, and make the repository -writable by that group: - ------------------------------------------------- -$ chgrp -R $group repo.git -$ find repo.git -mindepth 1 -type d |xargs chmod ug+rwx,g+s -$ GIT_DIR=repo.git git repo-config core.sharedrepository true ------------------------------------------------- - -Make sure committers have a umask of at most 027, so that the directories -they create are writable and searchable by other group members. - -Performing Development on a Shared Repository ---------------------------------------------- - -Suppose a repository is now set up in /pub/repo.git on the host +Suppose a shared repository is set up in /pub/repo.git on the host foo.com. Then as an individual committer you can clone the shared -repository: +repository over ssh with: ------------------------------------------------ $ git clone foo.com:/pub/repo.git/ my-project @@ -129,7 +29,8 @@ $ git pull origin ------------------------------------------------ which merges in any work that others might have done since the clone -operation. +operation. If there are uncommitted changes in your working tree, commit +them first before running git pull. [NOTE] ================================ @@ -137,8 +38,8 @@ The first `git clone` places the following in the `my-project/.git/remotes/origin` file, and that's why the previous step and the next step both work. ------------ -URL: foo.com:/pub/project.git/ my-project -Pull: master:origin +URL: foo.com:/pub/project.git/ +Pull: refs/heads/master:refs/remotes/origin/master ------------ ================================ @@ -161,21 +62,76 @@ in the local repository. So the last `push` can be done with either of: ------------ $ git push origin -$ git push repo.shared.xz:/pub/scm/project.git/ +$ git push foo.com:/pub/project.git/ ------------ as long as the shared repository does not have any branches other than `master`. -[NOTE] -============ -Because of this behavior, if the shared repository and the developer's -repository both have branches named `origin`, then a push like the above -attempts to update the `origin` branch in the shared repository from the -developer's `origin` branch. The results may be unexpected, so it's -usually best to remove any branch named `origin` from the shared -repository. -============ +Setting Up a Shared Repository +------------------------------ + +We assume you have already created a git repository for your project, +possibly created from scratch or from a tarball (see the +link:tutorial.html[tutorial]), or imported from an already existing CVS +repository (see the next section). + +If your project's working directory is /home/alice/myproject, you can +create a shared repository at /pub/repo.git with: + +------------------------------------------------ +$ git clone -bare /home/alice/myproject /pub/repo.git +------------------------------------------------ + +Next, give every team member read/write access to this repository. One +easy way to do this is to give all the team members ssh access to the +machine where the repository is hosted. If you don't want to give them a +full shell on the machine, there is a restricted shell which only allows +users to do git pushes and pulls; see gitlink:git-shell[1]. + +Put all the committers in the same group, and make the repository +writable by that group: + +------------------------------------------------ +$ cd /pub +$ chgrp -R $group repo.git +$ find repo.git -mindepth 1 -type d |xargs chmod ug+rwx,g+s +$ GIT_DIR=repo.git git repo-config core.sharedrepository true +------------------------------------------------ + +Make sure committers have a umask of at most 027, so that the directories +they create are writable and searchable by other group members. + +Importing a CVS archive +----------------------- + +First, install version 2.1 or higher of cvsps from +link:http://www.cobite.com/cvsps/[http://www.cobite.com/cvsps/] and make +sure it is in your path. The magic command line is then + +------------------------------------------- +$ git cvsimport -v -d -C +------------------------------------------- + +This puts a git archive of the named CVS module in the directory +, which will be created if necessary. The -v option makes +the conversion script very chatty. + +The import checks out from CVS every revision of every file. Reportedly +cvsimport can average some twenty revisions per second, so for a +medium-sized project this should not take more than a couple of minutes. +Larger projects or remote repositories may take longer. + +The main trunk is stored in the git branch named `origin`, and additional +CVS branches are stored in git branches with the same names. The most +recent version of the main trunk is also left checked out on the `master` +branch, so you can start adding your own changes right away. + +The import is incremental, so if you call it again next month it will +fetch any CVS updates that have been made in the meantime. For this to +work, you must not modify the imported branches; instead, create new +branches for your own changes, and merge in the imported branches as +necessary. Advanced Shared Repository Management ------------------------------------- @@ -188,127 +144,30 @@ You can enforce finer grained permissions using update hooks. See link:howto/update-hook-example.txt[Controlling access to branches using update hooks]. -CVS annotate ------------- +Providing CVS Access to a git Repository +---------------------------------------- + +It is also possible to provide true CVS access to a git repository, so +that developers can still use CVS; see gitlink:git-cvsserver[1] for +details. + +Alternative Development Models +------------------------------ + +CVS users are accustomed to giving a group of developers commit access to +a common repository. As we've seen, this is also possible with git. +However, the distributed nature of git allows other development models, +and you may want to first consider whether one of them might be a better +fit for your project. + +For example, you can choose a single person to maintain the project's +primary public repository. Other developers then clone this repository +and each work in their own clone. When they have a series of changes that +they're happy with, they ask the maintainer to pull from the branch +containing the changes. The maintainer reviews their changes and pulls +them into the primary repository, which other developers pull from as +necessary to stay coordinated. The Linux kernel and other projects use +variants of this model. -So, something has gone wrong, and you don't know whom to blame, and -you're an ex-CVS user and used to do "cvs annotate" to see who caused -the breakage. You're looking for the "git annotate", and it's just -claiming not to find such a script. You're annoyed. - -Yes, that's right. Core git doesn't do "annotate", although it's -technically possible, and there are at least two specialized scripts out -there that can be used to get equivalent information (see the git -mailing list archives for details). - -git has a couple of alternatives, though, that you may find sufficient -or even superior depending on your use. One is called "git-whatchanged" -(for obvious reasons) and the other one is called "pickaxe" ("a tool for -the software archaeologist"). - -The "git-whatchanged" script is a truly trivial script that can give you -a good overview of what has changed in a file or a directory (or an -arbitrary list of files or directories). The "pickaxe" support is an -additional layer that can be used to further specify exactly what you're -looking for, if you already know the specific area that changed. - -Let's step back a bit and think about the reason why you would -want to do "cvs annotate a-file.c" to begin with. - -You would use "cvs annotate" on a file when you have trouble -with a function (or even a single "if" statement in a function) -that happens to be defined in the file, which does not do what -you want it to do. And you would want to find out why it was -written that way, because you are about to modify it to suit -your needs, and at the same time you do not want to break its -current callers. For that, you are trying to find out why the -original author did things that way in the original context. - -Many times, it may be enough to see the commit log messages of -commits that touch the file in question, possibly along with the -patches themselves, like this: - - $ git-whatchanged -p a-file.c - -This will show log messages and patches for each commit that -touches a-file. - -This, however, may not be very useful when this file has many -modifications that are not related to the piece of code you are -interested in. You would see many log messages and patches that -do not have anything to do with the piece of code you are -interested in. As an example, assuming that you have this piece -of code that you are interested in in the HEAD version: - - if (frotz) { - nitfol(); - } - -you would use git-rev-list and git-diff-tree like this: - - $ git-rev-list HEAD | - git-diff-tree --stdin -v -p -S'if (frotz) { - nitfol(); - }' - -We have already talked about the "\--stdin" form of git-diff-tree -command that reads the list of commits and compares each commit -with its parents (otherwise you should go back and read the tutorial). -The git-whatchanged command internally runs -the equivalent of the above command, and can be used like this: - - $ git-whatchanged -p -S'if (frotz) { - nitfol(); - }' - -When the -S option is used, git-diff-tree command outputs -differences between two commits only if one tree has the -specified string in a file and the corresponding file in the -other tree does not. The above example looks for a commit that -has the "if" statement in it in a file, but its parent commit -does not have it in the same shape in the corresponding file (or -the other way around, where the parent has it and the commit -does not), and the differences between them are shown, along -with the commit message (thanks to the -v flag). It does not -show anything for commits that do not touch this "if" statement. - -Also, in the original context, the same statement might have -appeared at first in a different file and later the file was -renamed to "a-file.c". CVS annotate would not help you to go -back across such a rename, but git would still help you in such -a situation. For that, you can give the -C flag to -git-diff-tree, like this: - - $ git-whatchanged -p -C -S'if (frotz) { - nitfol(); - }' - -When the -C flag is used, file renames and copies are followed. -So if the "if" statement in question happens to be in "a-file.c" -in the current HEAD commit, even if the file was originally -called "o-file.c" and then renamed in an earlier commit, or if -the file was created by copying an existing "o-file.c" in an -earlier commit, you will not lose track. If the "if" statement -did not change across such a rename or copy, then the commit that -does rename or copy would not show in the output, and if the -"if" statement was modified while the file was still called -"o-file.c", it would find the commit that changed the statement -when it was in "o-file.c". - -NOTE: The current version of "git-diff-tree -C" is not eager - enough to find copies, and it will miss the fact that a-file.c - was created by copying o-file.c unless o-file.c was somehow - changed in the same commit. - -You can use the --pickaxe-all flag in addition to the -S flag. -This causes the differences from all the files contained in -those two commits, not just the differences between the files -that contain this changed "if" statement: - - $ git-whatchanged -p -C -S'if (frotz) { - nitfol(); - }' --pickaxe-all - -NOTE: This option is called "--pickaxe-all" because -S - option is internally called "pickaxe", a tool for software - archaeologists. +With a small group, developers may just pull changes from each other's +repositories without the need for a central maintainer. -- cgit v0.10.2-6-g49f6 From 46732fae3d049254f4f12b8a716cf56159277eda Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 6 Dec 2006 23:01:00 -0500 Subject: change the unpack limit treshold to a saner value Currently the treshold is 5000. The likelihood of this value to ever be crossed for a single push is really small making it not really useful. The optimal treshold for a pure space saving on a filesystem with 4kb blocks is 3. However this is likely to create many small packs concentrating a large number of files in a single directory compared to the same objects which are spread over 256 directories when loose. This means we would need 512 objects per pack on average to approximagte the same directory cost (a pack has 2 files because of the index). But 512 is a really high value just like 5000 since most pushes are unlikely to have that many objects. So let's try with a value of 100 which should have a good balance between small pushes going to be exploded into loose objects and large pushes kept as whole packs. This is not a replacement for periodic repacks of course. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/receive-pack.c b/receive-pack.c index a20bc92..e76d9ae 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -11,7 +11,7 @@ static const char receive_pack_usage[] = "git-receive-pack "; static int deny_non_fast_forwards = 0; -static int unpack_limit = 5000; +static int unpack_limit = 100; static int report_status; static char capabilities[] = " report-status delete-refs "; -- cgit v0.10.2-6-g49f6 From 4f88d3e0cbf443cd309c2c881209f3366f14023d Mon Sep 17 00:00:00 2001 From: Martin Langhoff Date: Thu, 7 Dec 2006 16:38:50 +1300 Subject: cvsserver: Avoid miscounting bytes in Perl v5.8.x At some point between v5.6 and 5.8 Perl started to assume its input, output and filehandles are UTF-8. This breaks the counting of bytes for the CVS protocol, resulting in the client expecting less data than we actually send, and storing truncated files. Signed-off-by: Martin Langhoff Signed-off-by: Junio C Hamano diff --git a/git-cvsserver.perl b/git-cvsserver.perl index ca519b7..197014d 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -17,6 +17,7 @@ use strict; use warnings; +use bytes; use Fcntl; use File::Temp qw/tempdir tempfile/; -- cgit v0.10.2-6-g49f6 From db9819a40a56b4747931e637c1c22a104dcab902 Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Fri, 8 Dec 2006 01:27:21 -0500 Subject: Documentation: update git-clone man page with new behavior Update git-clone man page to reflect recent changes (--use-separate-remote default and use of .git/config instead of remotes files), and rewrite introduction. Signed-off-by: J. Bruce Fields Signed-off-by: Junio C Hamano diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index d5efa00..985043f 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -16,22 +16,21 @@ SYNOPSIS DESCRIPTION ----------- -Clones a repository into a newly created directory. All remote -branch heads are copied under `$GIT_DIR/refs/heads/`, except -that the remote `master` is also copied to `origin` branch. -In addition, `$GIT_DIR/remotes/origin` file is set up to have -this line: +Clones a repository into a newly created directory, creates +remote-tracking branches for each branch in the cloned repository +(visible using `git branch -r`), and creates and checks out a master +branch equal to the cloned repository's master branch. - Pull: master:origin - -This is to help the typical workflow of working off of the -remote `master` branch. Every time `git pull` without argument -is run, the progress on the remote `master` branch is tracked by -copying it into the local `origin` branch, and merged into the -branch you are currently working on. Remote branches other than -`master` are also added there to be tracked. +After the clone, a plain `git fetch` without arguments will update +all the remote-tracking branches, and a `git pull` without +arguments will in addition merge the remote master branch into the +current branch. +This default configuration is achieved by creating references to +the remote branch heads under `$GIT_DIR/refs/remotes/origin` and +by initializing `remote.origin.url` and `remote.origin.fetch` +configuration variables. OPTIONS ------- -- cgit v0.10.2-6-g49f6 From 006ede5e860717ff1ec68125393bcd4e74507e5b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 8 Dec 2006 01:55:19 -0800 Subject: git-svn: extra error check to ensure we open a file correctly This may be an issue with repositories imported with commit 27a1a8014b842c0d70fdc91c68dd361ca2dfb34c or later, but before commit dad73c0bb9f33323ec1aacf560a6263f1d85f81a. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index 747daf0..ff61b92 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -3438,6 +3438,9 @@ sub open_file { my ($self, $path, $pb, $rev) = @_; my ($mode, $blob) = (safe_qx('git-ls-tree',$self->{c},'--',$path) =~ /^(\d{6}) blob ([a-f\d]{40})\t/); + unless (defined $mode && defined $blob) { + die "$path was not found in commit $self->{c} (r$rev)\n"; + } { path => $path, mode_a => $mode, mode_b => $mode, blob => $blob, pool => SVN::Pool->new, action => 'M' }; } -- cgit v0.10.2-6-g49f6 From bbee1d971dc07c29f840b439aa2a2c890a12cf9f Mon Sep 17 00:00:00 2001 From: Uwe Zeisberger Date: Fri, 8 Dec 2006 12:44:31 +0100 Subject: Fix documentation copy&paste typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This was introduced in 45a3b12cfd3eaa05bbb0954790d5be5b8240a7b5 Signed-off-by: Uwe Kleine-K,AC6(Bnig Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 61e2ab2..5ea3fda 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -120,7 +120,7 @@ our %feature = ( # To disable system wide have in $GITWEB_CONFIG # $feature{'snapshot'}{'default'} = [undef]; # To have project specific config enable override in $GITWEB_CONFIG - # $feature{'blame'}{'override'} = 1; + # $feature{'snapshot'}{'override'} = 1; # and in project config gitweb.snapshot = none|gzip|bzip2; 'snapshot' => { 'sub' => \&feature_snapshot, -- cgit v0.10.2-6-g49f6 From a552db3a64464f1b514b074fbc43aaf599106087 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 8 Dec 2006 02:20:17 -0800 Subject: git-svn: use do_switch for --follow-parent if the SVN library supports it do_switch works with the SVN Perl bindings after r22312 in the Subversion trunk. Since no released version of SVN currently supports it; we'll just autodetect it and enable its usage when a user has a recent-enough version of SVN. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index ff61b92..1f8a3b0 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -72,7 +72,7 @@ my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit, $_username, $_config_dir, $_no_auth_cache, $_xfer_delta, $_pager, $_color); my (@_branch_from, %tree_map, %users, %rusers, %equiv); -my ($_svn_co_url_revs, $_svn_pg_peg_revs); +my ($_svn_co_url_revs, $_svn_pg_peg_revs, $_svn_can_do_switch); my @repo_path_split_cache; my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext, @@ -2877,6 +2877,24 @@ sub libsvn_connect { return $ra; } +sub libsvn_can_do_switch { + unless (defined $_svn_can_do_switch) { + my $pool = SVN::Pool->new; + my $rep = eval { + $SVN->do_switch(1, '', 0, $SVN->{url}, + SVN::Delta::Editor->new, $pool); + }; + if ($@) { + $_svn_can_do_switch = 0; + } else { + $rep->abort_report($pool); + $_svn_can_do_switch = 1; + } + $pool->clear; + } + $_svn_can_do_switch; +} + sub libsvn_dup_ra { my ($ra) = @_; SVN::Ra->new(map { $_ => $ra->{$_} } qw/config url @@ -3198,12 +3216,26 @@ sub libsvn_find_parent_branch { unlink $GIT_SVN_INDEX; print STDERR "Found branch parent: ($GIT_SVN) $parent\n"; sys(qw/git-read-tree/, $parent); - # I can't seem to get do_switch() to work correctly with - # the SWIG interface (TypeError when passing switch_url...), - # so we'll unconditionally bypass the delta interface here - # for now - return libsvn_fetch_full($parent, $paths, $rev, - $author, $date, $msg); + unless (libsvn_can_do_switch()) { + return libsvn_fetch_full($parent, $paths, $rev, + $author, $date, $msg); + } + # do_switch works with svn/trunk >= r22312, but that is not + # included with SVN 1.4.2 (the latest version at the moment), + # so we can't rely on it. + my $ra = libsvn_connect("$url/$branch_from"); + my $ed = SVN::Git::Fetcher->new({c => $parent, q => $_q}); + my $pool = SVN::Pool->new; + my $reporter = $ra->do_switch($rev, '', 1, $SVN->{url}, + $ed, $pool); + my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); + $reporter->set_path('', $r0, 0, @lock, $pool); + $reporter->finish_report($pool); + $pool->clear; + unless ($ed->{git_commit_ok}) { + die "SVN connection failed somewhere...\n"; + } + return libsvn_log_entry($rev, $author, $date, $msg, [$parent]); } print STDERR "Nope, branch point not imported or unknown\n"; return undef; -- cgit v0.10.2-6-g49f6 From 2cdf87ebd9976d98d544669d94b111fea731d2ba Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 8 Dec 2006 14:07:45 -0800 Subject: Fix perl/ build. An earlier commit f848718a broke the build in perl/ directory by allowing the Makefile.PL to overwrite the now-tracked Makefile. Fix this by forcing Makefile.PL to produce its output in perl.mak as the broken commit originally intended. Signed-off-by: Junio C Hamano diff --git a/perl/Makefile b/perl/Makefile index bd483b0..099beda 100644 --- a/perl/Makefile +++ b/perl/Makefile @@ -29,7 +29,7 @@ $(makfile): ../GIT-CFLAGS Makefile echo ' echo $(instdir_SQ)' >> $@ else $(makfile): Makefile.PL ../GIT-CFLAGS - '$(PERL_PATH_SQ)' $< FIRST_MAKEFILE='$@' PREFIX='$(prefix_SQ)' + '$(PERL_PATH_SQ)' $< PREFIX='$(prefix_SQ)' endif # this is just added comfort for calling make directly in perl dir diff --git a/perl/Makefile.PL b/perl/Makefile.PL index de73235..4168775 100644 --- a/perl/Makefile.PL +++ b/perl/Makefile.PL @@ -24,5 +24,6 @@ WriteMakefile( NAME => 'Git', VERSION_FROM => 'Git.pm', PM => \%pm, + MAKEFILE => 'perl.mak', %extra ); -- cgit v0.10.2-6-g49f6 From 62b339a544b1fa5199de7571c460d770cb286e65 Mon Sep 17 00:00:00 2001 From: Josef Weidendorfer Date: Sat, 9 Dec 2006 02:28:26 +0100 Subject: Add branch.*.merge warning and documentation update This patch clarifies the meaning of the branch.*.merge option. Previously, if branch.*.merge was specified but did not match any ref, the message "No changes." was not really helpful regarding the misconfiguration. This patch adds a warning for this. Signed-off-by: Josef Weidendorfer Signed-off-by: Junio C Hamano diff --git a/Documentation/config.txt b/Documentation/config.txt index 9090762..21ec557 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -125,10 +125,17 @@ apply.whitespace:: branch..remote:: When in branch , it tells `git fetch` which remote to fetch. + If this option is not given, `git fetch` defaults to remote "origin". branch..merge:: - When in branch , it tells `git fetch` the default remote branch - to be merged. + When in branch , it tells `git fetch` the default refspec to + be marked for merging in FETCH_HEAD. The value has exactly to match + a remote part of one of the refspecs which are fetched from the remote + given by "branch..remote". + The merge information is used by `git pull` (which at first calls + `git fetch`) to lookup the default branch for merging. Without + this option, `git pull` defaults to merge the first refspec fetched. + Specify multiple values to get an octopus merge. pager.color:: A boolean to enable/disable colored output when the pager is in diff --git a/git-parse-remote.sh b/git-parse-remote.sh index da064a5..6ae534b 100755 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -134,6 +134,8 @@ canon_refs_list_for_fetch () { # or the first one otherwise; add prefix . to the rest # to prevent the secondary branches to be merged by default. merge_branches= + found_mergeref= + curr_branch= if test "$1" = "-d" then shift ; remote="$1" ; shift @@ -171,6 +173,10 @@ canon_refs_list_for_fetch () { dot_prefix= && break done fi + if test -z $dot_prefix + then + found_mergeref=true + fi case "$remote" in '') remote=HEAD ;; refs/heads/* | refs/tags/* | refs/remotes/*) ;; @@ -191,6 +197,11 @@ canon_refs_list_for_fetch () { fi echo "${dot_prefix}${force}${remote}:${local}" done + if test -z "$found_mergeref" -a "$curr_branch" + then + echo >&2 "Warning: No merge candidate found because value of config option + \"branch.${curr_branch}.merge\" does not match any remote branch fetched." + fi } # Returns list of src: (no store), or src:dst (store) -- cgit v0.10.2-6-g49f6 From 90ffefe564cd849f88b1d1b5817eb25e3d57521b Mon Sep 17 00:00:00 2001 From: Jeff King Date: Fri, 8 Dec 2006 23:04:21 -0500 Subject: shortlog: fix segfault on empty authorname The old code looked backwards from the email address to parse the name, allowing an arbitrary number of spaces between the two. However, in the case of no name, we looked back too far to the 'author' (or 'Author:') header. Instead, remove at most one space between name and address. The bug was triggered by commit febf7ea4bed from linux-2.6. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/builtin-shortlog.c b/builtin-shortlog.c index f1124e2..7a2ddfe 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -188,7 +188,7 @@ static void read_from_stdin(struct path_list *list) bob = buffer + strlen(buffer); else { offset = 8; - while (isspace(bob[-1])) + if (isspace(bob[-1])) bob--; } @@ -236,7 +236,7 @@ static void get_from_rev(struct rev_info *rev, struct path_list *list) author = scratch; authorlen = strlen(scratch); } else { - while (bracket[-1] == ' ') + if (bracket[-1] == ' ') bracket--; author = buffer + 7; -- cgit v0.10.2-6-g49f6 From 4cfeccc75d6ab1ccc433770bac6bf3b15ab486d6 Mon Sep 17 00:00:00 2001 From: "J. Bruce Fields" Date: Fri, 8 Dec 2006 22:58:50 -0500 Subject: Documentation: simpler shared repository creation Take Johannes Schindelin's suggestions for a further simplification of the shared repository creation using git --bare init-db --shared, and for a simplified cvsimport using an existing CVS working directory. Also insert more man page references. Signed-off-by: J. Bruce Fields cvs-migration.txt | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) Signed-off-by: Junio C Hamano diff --git a/Documentation/cvs-migration.txt b/Documentation/cvs-migration.txt index 47846bd..b657f45 100644 --- a/Documentation/cvs-migration.txt +++ b/Documentation/cvs-migration.txt @@ -43,8 +43,8 @@ Pull: refs/heads/master:refs/remotes/origin/master ------------ ================================ -You can update the shared repository with your changes by first commiting -your changes, and then using: +You can update the shared repository with your changes by first committing +your changes, and then using the gitlink:git-push[1] command: ------------------------------------------------ $ git push origin master @@ -76,11 +76,15 @@ possibly created from scratch or from a tarball (see the link:tutorial.html[tutorial]), or imported from an already existing CVS repository (see the next section). -If your project's working directory is /home/alice/myproject, you can -create a shared repository at /pub/repo.git with: +Assume your existing repo is at /home/alice/myproject. Create a new "bare" +repository (a repository without a working tree) and fetch your project into +it: ------------------------------------------------ -$ git clone -bare /home/alice/myproject /pub/repo.git +$ mkdir /pub/my-repo.git +$ cd /pub/my-repo.git +$ git --bare init-db --shared +$ git --bare fetch /home/alice/myproject master:master ------------------------------------------------ Next, give every team member read/write access to this repository. One @@ -93,10 +97,7 @@ Put all the committers in the same group, and make the repository writable by that group: ------------------------------------------------ -$ cd /pub -$ chgrp -R $group repo.git -$ find repo.git -mindepth 1 -type d |xargs chmod ug+rwx,g+s -$ GIT_DIR=repo.git git repo-config core.sharedrepository true +$ chgrp -R $group /pub/my-repo.git ------------------------------------------------ Make sure committers have a umask of at most 027, so that the directories @@ -107,15 +108,15 @@ Importing a CVS archive First, install version 2.1 or higher of cvsps from link:http://www.cobite.com/cvsps/[http://www.cobite.com/cvsps/] and make -sure it is in your path. The magic command line is then +sure it is in your path. Then cd to a checked out CVS working directory +of the project you are interested in and run gitlink:git-cvsimport[1]: ------------------------------------------- -$ git cvsimport -v -d -C +$ git cvsimport -C ------------------------------------------- This puts a git archive of the named CVS module in the directory -, which will be created if necessary. The -v option makes -the conversion script very chatty. +, which will be created if necessary. The import checks out from CVS every revision of every file. Reportedly cvsimport can average some twenty revisions per second, so for a -- cgit v0.10.2-6-g49f6 From d9671b75ad3bbf2f95f11a8571e9beaa12ccf6dd Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 8 Dec 2006 13:29:55 -0800 Subject: rerere: add clear, diff, and status commands git-am and git-rebase will be updated to use 'clear', and diff/status can be used to aid the user in tracking progress in the resolution process. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/Documentation/git-rerere.txt b/Documentation/git-rerere.txt index 8b6b651..22494b2 100644 --- a/Documentation/git-rerere.txt +++ b/Documentation/git-rerere.txt @@ -7,8 +7,7 @@ git-rerere - Reuse recorded resolve SYNOPSIS -------- -'git-rerere' - +'git-rerere' [clear|diff|status] DESCRIPTION ----------- @@ -167,6 +166,30 @@ would conflict the same way the test merge you resolved earlier. `git-rerere` is run by `git rebase` to help you resolve this conflict. +COMMANDS +-------- + +Normally, git-rerere is run without arguments or user-intervention. +However, it has several commands that allow it to interact with +its working state. + +'clear':: + +This resets the metadata used by rerere if a merge resolution is to be +is aborted. Calling gitlink:git-am[1] --skip or gitlink:git-rebase[1] +[--skip|--abort] will automatcally invoke this command. + +'diff':: + +This displays diffs for the current state of the resolution. It is +useful for tracking what has changed while the user is resolving +conflicts. Additional arguments are passed directly to the system +diff(1) command installed in PATH. + +'status':: + +Like diff, but this only prints the filenames that will be tracked +for resolutions. Author ------ diff --git a/git-rerere.perl b/git-rerere.perl index d3664ff..b2550bb 100755 --- a/git-rerere.perl +++ b/git-rerere.perl @@ -172,6 +172,38 @@ sub merge { -d "$rr_dir" || exit(0); read_rr(); + +if (@ARGV) { + my $arg = shift @ARGV; + if ($arg eq 'clear') { + for my $path (keys %merge_rr) { + my $name = $merge_rr{$path}; + if (-d "$rr_dir/$name" && + ! -f "$rr_dir/$name/postimage") { + rmtree(["$rr_dir/$name"]); + } + } + unlink $merge_rr; + } + elsif ($arg eq 'status') { + for my $path (keys %merge_rr) { + print $path, "\n"; + } + } + elsif ($arg eq 'diff') { + for my $path (keys %merge_rr) { + my $name = $merge_rr{$path}; + system('diff', ((@ARGV == 0) ? ('-u') : @ARGV), + '-L', "a/$path", '-L', "b/$path", + "$rr_dir/$name/preimage", $path); + } + } + else { + die "$0 unknown command: $arg\n"; + } + exit 0; +} + my %conflict = map { $_ => 1 } find_conflict(); # MERGE_RR records paths with conflicts immediately after merge -- cgit v0.10.2-6-g49f6 From cda2d3c112a03079af9019c7d6617e65ab88ae7e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 8 Dec 2006 13:03:12 -0800 Subject: git-rerere: add 'gc' command. Over time, unresolved rr-cache entries are accumulated and they tend to get less and less likely to be useful as the tips of branches advance. Reorder documentation page to show the subcommand section earlier than the discussion section. Signed-off-by: Junio C Hamano diff --git a/Documentation/git-rerere.txt b/Documentation/git-rerere.txt index 22494b2..116dca4 100644 --- a/Documentation/git-rerere.txt +++ b/Documentation/git-rerere.txt @@ -26,6 +26,38 @@ results and applying the previously recorded hand resolution. You need to create `$GIT_DIR/rr-cache` directory to enable this command. + +COMMANDS +-------- + +Normally, git-rerere is run without arguments or user-intervention. +However, it has several commands that allow it to interact with +its working state. + +'clear':: + +This resets the metadata used by rerere if a merge resolution is to be +is aborted. Calling gitlink:git-am[1] --skip or gitlink:git-rebase[1] +[--skip|--abort] will automatcally invoke this command. + +'diff':: + +This displays diffs for the current state of the resolution. It is +useful for tracking what has changed while the user is resolving +conflicts. Additional arguments are passed directly to the system +diff(1) command installed in PATH. + +'status':: + +Like diff, but this only prints the filenames that will be tracked +for resolutions. + +'gc':: + +This command is used to prune records of conflicted merge that +occurred long time ago. + + DISCUSSION ---------- @@ -166,30 +198,6 @@ would conflict the same way the test merge you resolved earlier. `git-rerere` is run by `git rebase` to help you resolve this conflict. -COMMANDS --------- - -Normally, git-rerere is run without arguments or user-intervention. -However, it has several commands that allow it to interact with -its working state. - -'clear':: - -This resets the metadata used by rerere if a merge resolution is to be -is aborted. Calling gitlink:git-am[1] --skip or gitlink:git-rebase[1] -[--skip|--abort] will automatcally invoke this command. - -'diff':: - -This displays diffs for the current state of the resolution. It is -useful for tracking what has changed while the user is resolving -conflicts. Additional arguments are passed directly to the system -diff(1) command installed in PATH. - -'status':: - -Like diff, but this only prints the filenames that will be tracked -for resolutions. Author ------ diff --git a/git-rerere.perl b/git-rerere.perl index b2550bb..61eef57 100755 --- a/git-rerere.perl +++ b/git-rerere.perl @@ -169,6 +169,28 @@ sub merge { return 0; } +sub garbage_collect_rerere { + # We should allow specifying these from the command line and + # that is why the caller gives @ARGV to us, but I am lazy. + + my $cutoff_noresolve = 15; # two weeks + my $cutoff_resolve = 60; # two months + my @to_remove; + while (<$rr_dir/*/preimage>) { + my ($dir) = /^(.*)\/preimage$/; + my $cutoff = ((-f "$dir/postimage") + ? $cutoff_resolve + : $cutoff_noresolve); + my $age = -M "$_"; + if ($cutoff <= $age) { + push @to_remove, $dir; + } + } + if (@to_remove) { + rmtree(\@to_remove); + } +} + -d "$rr_dir" || exit(0); read_rr(); @@ -198,6 +220,9 @@ if (@ARGV) { "$rr_dir/$name/preimage", $path); } } + elsif ($arg eq 'gc') { + garbage_collect_rerere(@ARGV); + } else { die "$0 unknown command: $arg\n"; } -- cgit v0.10.2-6-g49f6 From f131dd492f098f9f565df93df13e35c734284590 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 8 Dec 2006 13:29:56 -0800 Subject: rerere: record (or avoid misrecording) resolved, skipped or aborted rebase/am Data in rr-cache isn't valid after a patch application is skipped or and aborted, so our next commit could be misrecorded as a resolution of that skipped/failed commit, which is wrong. git-am --skip, git-rebase --skip/--abort will automatically invoke git-rerere clear to avoid this. Also, since git-am --resolved indicates a resolution was succesful, remember to run git-rerere to record the resolution (and not surprise the user when the next commit is made). Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-am.sh b/git-am.sh index afe322b..5df6787 100755 --- a/git-am.sh +++ b/git-am.sh @@ -246,6 +246,10 @@ last=`cat "$dotest/last"` this=`cat "$dotest/next"` if test "$skip" = t then + if test -d "$GIT_DIR/rr-cache" + then + git-rerere clear + fi this=`expr "$this" + 1` resume= fi @@ -408,6 +412,10 @@ do stop_here_user_resolve $this fi apply_status=0 + if test -d "$GIT_DIR/rr-cache" + then + git rerere + fi ;; esac diff --git a/git-rebase.sh b/git-rebase.sh index 25530df..2b4f347 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -139,6 +139,10 @@ do --skip) if test -d "$dotest" then + if test -d "$GIT_DIR/rr-cache" + then + git-rerere clear + fi prev_head="`cat $dotest/prev_head`" end="`cat $dotest/end`" msgnum="`cat $dotest/msgnum`" @@ -157,6 +161,10 @@ do exit ;; --abort) + if test -d "$GIT_DIR/rr-cache" + then + git-rerere clear + fi if test -d "$dotest" then rm -r "$dotest" -- cgit v0.10.2-6-g49f6 From 6c96753df9db7f790a2ac4d95ec2a868394cd5ff Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 8 Dec 2006 21:48:07 -0800 Subject: Documentation/git-commit: rewrite to make it more end-user friendly. Signed-off-by: Junio C Hamano diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 517a86b..97d66ef 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -14,25 +14,41 @@ SYNOPSIS DESCRIPTION ----------- -Updates the index file for given paths, or all modified files if -'-a' is specified, and makes a commit object. The command specified -by either the VISUAL or EDITOR environment variables are used to edit -the commit log message. +Use 'git commit' when you want to record your changes into the repository +along with a log message describing what the commit is about. All changes +to be committed must be explicitly identified using one of the following +methods: -Several environment variable are used during commits. They are -documented in gitlink:git-commit-tree[1]. +1. by using gitlink:git-add[1] to incrementally "add" changes to the + next commit before using the 'commit' command (Note: even modified + files must be "added"); +2. by using gitlink:git-rm[1] to identify content removal for the next + commit, again before using the 'commit' command; + +3. by directly listing files containing changes to be committed as arguments + to the 'commit' command, in which cases only those files alone will be + considered for the commit; + +4. by using the -a switch with the 'commit' command to automatically "add" + changes from all known files i.e. files that have already been committed + before, and perform the actual commit. + +The gitlink:git-status[1] command can be used to obtain a +summary of what is included by any of the above for the next +commit by giving the same set of parameters you would give to +this command. + +If you make a commit and then found a mistake immediately after +that, you can recover from it with gitlink:git-reset[1]. -This command can run `commit-msg`, `pre-commit`, and -`post-commit` hooks. See link:hooks.html[hooks] for more -information. OPTIONS ------- -a|--all:: - Update all paths in the index file. This flag notices - files that have been modified and deleted, but new files - you have not told git about are not affected. + Tell the command to automatically stage files that have + been modified and deleted, but new files you have not + told git about are not affected. -c or -C :: Take existing commit object, and reuse the log message @@ -55,16 +71,13 @@ OPTIONS -s|--signoff:: Add Signed-off-by line at the end of the commit message. --v|--verify:: - Look for suspicious lines the commit introduces, and - abort committing if there is one. The definition of - 'suspicious lines' is currently the lines that has - trailing whitespaces, and the lines whose indentation - has a SP character immediately followed by a TAB - character. This is the default. - --n|--no-verify:: - The opposite of `--verify`. +--no-verify:: + By default, the command looks for suspicious lines the + commit introduces, and aborts committing if there is one. + The definition of 'suspicious lines' is currently the + lines that has trailing whitespaces, and the lines whose + indentation has a SP character immediately followed by a + TAB character. This option turns off the check. -e|--edit:: The message taken from file with `-F`, command line with @@ -95,69 +108,137 @@ but can be used to amend a merge commit. -- -i|--include:: - Instead of committing only the files specified on the - command line, update them in the index file and then - commit the whole index. This is the traditional - behavior. - --o|--only:: - Commit only the files specified on the command line. - This format cannot be used during a merge, nor when the - index and the latest commit does not match on the - specified paths to avoid confusion. + Before making a commit out of staged contents so far, + stage the contents of paths given on the command line + as well. This is usually not what you want unless you + are concluding a conflicted merge. \--:: Do not interpret any more arguments as options. ...:: - Files to be committed. The meaning of these is - different between `--include` and `--only`. Without - either, it defaults `--only` semantics. - -If you make a commit and then found a mistake immediately after -that, you can recover from it with gitlink:git-reset[1]. + When files are given on the command line, the command + commits the contents of the named files, without + recording the changes already staged. The contents of + these files are also staged for the next commit on top + of what have been staged before. -Discussion ----------- - -`git commit` without _any_ parameter commits the tree structure -recorded by the current index file. This is a whole-tree commit -even the command is invoked from a subdirectory. - -`git commit --include paths...` is equivalent to - - git update-index --remove paths... - git commit - -That is, update the specified paths to the index and then commit -the whole tree. - -`git commit paths...` largely bypasses the index file and -commits only the changes made to the specified paths. It has -however several safety valves to prevent confusion. - -. It refuses to run during a merge (i.e. when - `$GIT_DIR/MERGE_HEAD` exists), and reminds trained git users - that the traditional semantics now needs -i flag. - -. It refuses to run if named `paths...` are different in HEAD - and the index (ditto about reminding). Added paths are OK. - This is because an earlier `git diff` (not `git diff HEAD`) - would have shown the differences since the last `git - update-index paths...` to the user, and an inexperienced user - may mistakenly think that the changes between the index and - the HEAD (i.e. earlier changes made before the last `git - update-index paths...` was done) are not being committed. - -. It reads HEAD commit into a temporary index file, updates the - specified `paths...` and makes a commit. At the same time, - the real index file is also updated with the same `paths...`. +EXAMPLES +-------- +When recording your own work, the contents of modified files in +your working tree are temporarily stored to a staging area +called the "index" with gitlink:git-add[1]. Removal +of a file is staged with gitlink:git-rm[1]. After building the +state to be committed incrementally with these commands, `git +commit` (without any pathname parameter) is used to record what +has been staged so far. This is the most basic form of the +command. An example: + +------------ +$ edit hello.c +$ git rm goodbye.c +$ git add hello.c +$ git commit +------------ + +//////////// +We should fix 'git rm' to remove goodbye.c from both index and +working tree for the above example. +//////////// + +Instead of staging files after each individual change, you can +tell `git commit` to notice the changes to the files whose +contents are tracked in +your working tree and do corresponding `git add` and `git rm` +for you. That is, this example does the same as the earlier +example if there is no other change in your working tree: + +------------ +$ edit hello.c +$ rm goodbye.c +$ git commit -a +------------ + +The command `git commit -a` first looks at your working tree, +notices that you have modified hello.c and removed goodbye.c, +and performs necessary `git add` and `git rm` for you. + +After staging changes to many files, you can alter the order the +changes are recorded in, by giving pathnames to `git commit`. +When pathnames are given, the command makes a commit that +only records the changes made to the named paths: + +------------ +$ edit hello.c hello.h +$ git add hello.c hello.h +$ edit Makefile +$ git commit Makefile +------------ + +This makes a commit that records the modification to `Makefile`. +The changes staged for `hello.c` and `hello.h` are not included +in the resulting commit. However, their changes are not lost -- +they are still staged and merely held back. After the above +sequence, if you do: + +------------ +$ git commit +------------ + +this second commit would record the changes to `hello.c` and +`hello.h` as expected. + +After a merge (initiated by either gitlink:git-merge[1] or +gitlink:git-pull[1]) stops because of conflicts, cleanly merged +paths are already staged to be committed for you, and paths that +conflicted are left in unmerged state. You would have to first +check which paths are conflicting with gitlink:git-status[1] +and after fixing them manually in your working tree, you would +stage the result as usual with gitlink:git-add[1]: + +------------ +$ git status | grep unmerged +unmerged: hello.c +$ edit hello.c +$ git add hello.c +------------ + +After resolving conflicts and staging the result, `git ls-files -u` +would stop mentioning the conflicted path. When you are done, +run `git commit` to finally record the merge: + +------------ +$ git commit +------------ + +As with the case to record your own changes, you can use `-a` +option to save typing. One difference is that during a merge +resolution, you cannot use `git commit` with pathnames to +alter the order the changes are committed, because the merge +should be recorded as a single commit. In fact, the command +refuses to run when given pathnames (but see `-i` option). + + +ENVIRONMENT VARIABLES +--------------------- +The command specified by either the VISUAL or EDITOR environment +variables is used to edit the commit log message. + +HOOKS +----- +This command can run `commit-msg`, `pre-commit`, and +`post-commit` hooks. See link:hooks.html[hooks] for more +information. -`git commit --all` updates the index file with _all_ changes to -the working tree, and makes a whole-tree commit, regardless of -which subdirectory the command is invoked in. +SEE ALSO +-------- +gitlink:git-add[1], +gitlink:git-rm[1], +gitlink:git-mv[1], +gitlink:git-merge[1], +gitlink:git-commit-tree[1] Author ------ -- cgit v0.10.2-6-g49f6 From 158d0577891441c01457bbcaf45585d3b50f5d75 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 9 Dec 2006 22:32:43 -0800 Subject: git-commit: allow --only to lose what was staged earlier. The command used to have a safety valve to prevent this sequence: edit foo git update-index foo edit foo git diff foo git commit --only foo The reason for this was because an inexperienced user might mistakenly think what is shown with the last-minute diff contains all the change that is being committed (instead, what the user asked to check was an incremental diff since what has been staged so far). However, this turns out to only annoy people who know what they are doing. Inexperienced people would not be using the first "update-index" anyway, in which case they would see the full changes in the "git diff". Signed-off-by: Junio C Hamano diff --git a/git-commit.sh b/git-commit.sh index 81c3a0c..c829791 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -350,19 +350,9 @@ t,) refuse_partial "Cannot do a partial commit during a merge." fi TMP_INDEX="$GIT_DIR/tmp-index$$" - if test -z "$initial_commit" - then - # make sure index is clean at the specified paths, or - # they are additions. - dirty_in_index=`git-diff-index --cached --name-status \ - --diff-filter=DMTU HEAD -- "$@"` - test -z "$dirty_in_index" || - refuse_partial "Different in index and the last commit: -$dirty_in_index" - fi commit_only=`git-ls-files --error-unmatch -- "$@"` || exit - # Build the temporary index and update the real index + # Build a temporary index and update the real index # the same way. if test -z "$initial_commit" then -- cgit v0.10.2-6-g49f6 From d44c92d6ab4ded7a1960bb0b4a1da0c2fc102b89 Mon Sep 17 00:00:00 2001 From: Chris Wright Date: Sun, 10 Dec 2006 23:39:32 -0800 Subject: no need to install manpages as executable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No need to install manpages as executable. Noticed by Ville Skytt,Ad(B. Signed-off-by: Chris Wright Signed-off-by: Junio C Hamano diff --git a/Documentation/Makefile b/Documentation/Makefile index c00f5f6..d68bc4a 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -56,8 +56,8 @@ man7: $(DOC_MAN7) install: man $(INSTALL) -d -m755 $(DESTDIR)$(man1dir) $(DESTDIR)$(man7dir) - $(INSTALL) $(DOC_MAN1) $(DESTDIR)$(man1dir) - $(INSTALL) $(DOC_MAN7) $(DESTDIR)$(man7dir) + $(INSTALL) -m644 $(DOC_MAN1) $(DESTDIR)$(man1dir) + $(INSTALL) -m644 $(DOC_MAN7) $(DESTDIR)$(man7dir) # -- cgit v0.10.2-6-g49f6 From 554a2636f7c5125a83bb07194632445467d46c83 Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Mon, 11 Dec 2006 19:06:34 +0100 Subject: Don't use memcpy when source and dest. buffers may overlap git-index-pack can call memcpy with overlapping source and destination buffers. The patch below makes it use memmove instead. If you want to demonstrate a failure, add the following two lines + if (input_offset < input_len) + abort (); before the existing memcpy call (shown in the patch below), and then run this: (cd t; sh ./t5500-fetch-pack.sh) Signed-off-by: Jim Meyering Signed-off-by: Junio C Hamano diff --git a/index-pack.c b/index-pack.c index 8331d99..6d6c92b 100644 --- a/index-pack.c +++ b/index-pack.c @@ -96,7 +96,7 @@ static void flush(void) if (output_fd >= 0) write_or_die(output_fd, input_buffer, input_offset); SHA1_Update(&input_ctx, input_buffer, input_offset); - memcpy(input_buffer, input_buffer + input_offset, input_len); + memmove(input_buffer, input_buffer + input_offset, input_len); input_offset = 0; } } -- cgit v0.10.2-6-g49f6 From 9abd46a3471c2d58976e06a00e937b03672b98bc Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 7 Dec 2006 05:17:07 -0500 Subject: Make sure the empty tree exists when needed in merge-recursive. There are some baseless merge cases where git-merge-recursive will try to compare one of the branches against the empty tree. However most projects won't have the empty tree object in their object database as Git does not normally create empty tree objects. If the empty tree object is missing then the merge process will die, as it cannot load the object from the database. The error message may make the user think that their database is corrupt when its actually not. So instead we should just create the empty tree object whenever it is needed. If the object already exists as a loose object then no harm done. Otherwise that loose object will be pruned away later by either git-prune or git-prune-packed. Thanks goes to Junio for suggesting this fix. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/merge-recursive.c b/merge-recursive.c index cd2cc77..32e186c 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1238,7 +1238,7 @@ static int merge(struct commit *h1, tree->object.parsed = 1; tree->object.type = OBJ_TREE; - hash_sha1_file(NULL, 0, tree_type, tree->object.sha1); + write_sha1_file(NULL, 0, tree_type, tree->object.sha1); merged_common_ancestors = make_virtual_commit(tree, "ancestor"); } -- cgit v0.10.2-6-g49f6 From bca73251da5cc3e4bea71e28e0096a5cd662bbd9 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 10 Dec 2006 15:55:07 -0800 Subject: shortlog: remove "[PATCH]" prefix from shortlog output Originally noticed by Nicolas Pitre; the real cause was the code was prepared to deal with [PATCH] (and [PATCH n/m whatever]) prefixes but forgot that the string can be indented while acting as a filter. Signed-off-by: Junio C Hamano diff --git a/builtin-shortlog.c b/builtin-shortlog.c index 7a2ddfe..3322c3a 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -195,11 +195,17 @@ static void read_from_stdin(struct path_list *list) while (fgets(buffer2, sizeof(buffer2), stdin) && buffer2[0] != '\n') ; /* chomp input */ - if (fgets(buffer2, sizeof(buffer2), stdin)) + if (fgets(buffer2, sizeof(buffer2), stdin)) { + int l2 = strlen(buffer2); + int i; + for (i = 0; i < l2; i++) + if (!isspace(buffer2[i])) + break; insert_author_oneline(list, buffer + offset, bob - buffer - offset, - buffer2, strlen(buffer2)); + buffer2 + i, l2 - i); + } } } } -- cgit v0.10.2-6-g49f6 From 6f9872582246b9b8ee4bdc9f6a563b409aab1ecb Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 10 Dec 2006 15:51:54 -0800 Subject: shortlog: fix segfault on empty authorname The old code looked backwards from the email address to parse the name, allowing an arbitrary number of spaces between the two. However, in the case of no name, we looked back too far to the 'author' (or 'Author:') header. The bug was triggered by commit febf7ea4bed from linux-2.6. Jeff King originally fixed it by looking back only one character; Johannes Schindelin pointed out that we could try harder while at it to cope with commits with broken headers. Signed-off-by: Junio C Hamano diff --git a/builtin-shortlog.c b/builtin-shortlog.c index 3322c3a..3fc43dd 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -188,7 +188,8 @@ static void read_from_stdin(struct path_list *list) bob = buffer + strlen(buffer); else { offset = 8; - if (isspace(bob[-1])) + while (buffer + offset < bob && + isspace(bob[-1])) bob--; } -- cgit v0.10.2-6-g49f6 From e86ad71fe5f53ae4434566bd09ea4256090e5a3a Mon Sep 17 00:00:00 2001 From: Robin Rosenberg Date: Mon, 11 Dec 2006 00:30:06 +0100 Subject: Make cvsexportcommit work with filenames with spaces and non-ascii characters. This patch uses git-apply to do the patching which simplifies the code a lot and also uses one pass to git-diff. git-apply gives information on added, removed files as well as which files are binary. Removed the test for checking for matching binary files when deleting them since git-apply happily deletes the file. This is matter of taste since we allow some fuzz for text patches also. Error handling was cleaned up, but not much tested. Signed-off-by: Robin Rosenberg Signed-off-by: Junio C Hamano diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index c9d1d88..4863c91 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -2,9 +2,8 @@ # Known limitations: # - does not propagate permissions -# - tells "ready for commit" even when things could not be completed -# (not sure this is true anymore, more testing is needed) -# - does not handle whitespace in pathnames at all. +# - error handling has not been extensively tested +# use strict; use Getopt::Std; @@ -115,49 +114,40 @@ if ($opt_a) { } close MSG; -my (@afiles, @dfiles, @mfiles, @dirs); -my %amodes; -my @files = safe_pipe_capture('git-diff-tree', '-r', $parent, $commit); -#print @files; -$? && die "Error in git-diff-tree"; -foreach my $f (@files) { - chomp $f; - my @fields = split(m!\s+!, $f); - if ($fields[4] eq 'A') { - my $path = $fields[5]; - $amodes{$path} = $fields[1]; - push @afiles, $path; - # add any needed parent directories - $path = dirname $path; - while (!-d $path and ! grep { $_ eq $path } @dirs) { - unshift @dirs, $path; - $path = dirname $path; - } - } - if ($fields[4] eq 'M') { - push @mfiles, $fields[5]; - } - if ($fields[4] eq 'D') { - push @dfiles, $fields[5]; - } +`git-diff-tree --binary -p $parent $commit >.cvsexportcommit.diff`;# || die "Cannot diff"; + +## apply non-binary changes +my $fuzz = $opt_p ? 0 : 2; + +print "Checking if patch will apply\n"; + +my @stat; +open APPLY, "GIT_DIR= git-apply -C$fuzz --binary --summary --numstat<.cvsexportcommit.diff|" || die "cannot patch"; +@stat=; +close APPLY || die "Cannot patch"; +my (@bfiles,@files,@afiles,@dfiles); +chomp @stat; +foreach (@stat) { + push (@bfiles,$1) if m/^-\t-\t(.*)$/; + push (@files, $1) if m/^-\t-\t(.*)$/; + push (@files, $1) if m/^\d+\t\d+\t(.*)$/; + push (@afiles,$1) if m/^ create mode [0-7]+ (.*)$/; + push (@dfiles,$1) if m/^ delete mode [0-7]+ (.*)$/; } -my (@binfiles, @abfiles, @dbfiles, @bfiles, @mbfiles); -@binfiles = grep m/^Binary files/, safe_pipe_capture('git-diff-tree', '-p', $parent, $commit); -map { chomp } @binfiles; -@abfiles = grep s/^Binary files \/dev\/null and b\/(.*) differ$/$1/, @binfiles; -@dbfiles = grep s/^Binary files a\/(.*) and \/dev\/null differ$/$1/, @binfiles; -@mbfiles = grep s/^Binary files a\/(.*) and b\/(.*) differ$/$1/, @binfiles; -push @bfiles, @abfiles; -push @bfiles, @dbfiles; -push @bfiles, @mbfiles; -push @mfiles, @mbfiles; - -$opt_v && print "The commit affects:\n "; -$opt_v && print join ("\n ", @afiles,@mfiles,@dfiles) . "\n\n"; -undef @files; # don't need it anymore +map { s/^"(.*)"$/$1/g } @bfiles,@files; +map { s/\\([0-7]{3})/sprintf('%c',oct $1)/eg } @bfiles,@files; # check that the files are clean and up to date according to cvs my $dirty; +my @dirs; +foreach my $p (@afiles) { + my $path = dirname $p; + while (!-d $path and ! grep { $_ eq $path } @dirs) { + unshift @dirs, $path; + $path = dirname $path; + } +} + foreach my $d (@dirs) { if (-e $d) { $dirty = 1; @@ -180,7 +170,8 @@ foreach my $f (@afiles) { } } -foreach my $f (@mfiles, @dfiles) { +foreach my $f (@files) { + next if grep { $_ eq $f } @afiles; # TODO:we need to handle removed in cvs my @status = grep(m/^File/, safe_pipe_capture('cvs', '-q', 'status' ,$f)); if (@status > 1) { warn 'Strange! cvs status returned more than one line?'}; @@ -197,87 +188,26 @@ if ($dirty) { } } -### -### NOTE: if you are planning to die() past this point -### you MUST call cleanupcvs(@files) before die() -### +print "Applying\n"; +`GIT_DIR= git-apply -C$fuzz --binary --summary --numstat --apply <.cvsexportcommit.diff` || die "cannot patch"; - -print "Creating new directories\n"; +print "Patch applied successfully. Adding new files and directories to CVS\n"; +my $dirtypatch = 0; foreach my $d (@dirs) { - unless (mkdir $d) { - warn "Could not mkdir $d: $!"; - $dirty = 1; - } - `cvs add $d`; - if ($?) { - $dirty = 1; + if (system('cvs','add',$d)) { + $dirtypatch = 1; warn "Failed to cvs add directory $d -- you may need to do it manually"; } } -print "'Patching' binary files\n"; - -foreach my $f (@bfiles) { - # check that the file in cvs matches the "old" file - # extract the file to $tmpdir and compare with cmp - if (not(grep { $_ eq $f } @afiles)) { - my $tree = safe_pipe_capture('git-rev-parse', "$parent^{tree}"); - chomp $tree; - my $blob = `git-ls-tree $tree "$f" | cut -f 1 | cut -d ' ' -f 3`; - chomp $blob; - `git-cat-file blob $blob > $tmpdir/blob`; - if (system('cmp', '-s', $f, "$tmpdir/blob")) { - warn "Binary file $f in CVS does not match parent.\n"; - if (not $opt_f) { - $dirty = 1; - next; - } - } - } - if (not(grep { $_ eq $f } @dfiles)) { - my $tree = safe_pipe_capture('git-rev-parse', "$commit^{tree}"); - chomp $tree; - my $blob = `git-ls-tree $tree "$f" | cut -f 1 | cut -d ' ' -f 3`; - chomp $blob; - # replace with the new file - `git-cat-file blob $blob > $f`; - } - - # TODO: something smart with file modes - -} -if ($dirty) { - cleanupcvs(@files); - die "Exiting: Binary files in CVS do not match parent"; -} - -## apply non-binary changes -my $fuzz = $opt_p ? 0 : 2; - -print "Patching non-binary files\n"; - -if (scalar(@afiles)+scalar(@dfiles)+scalar(@mfiles) != scalar(@bfiles)) { - print `(git-diff-tree -p $parent -p $commit | patch -p1 -F $fuzz ) 2>&1`; -} - -my $dirtypatch = 0; -if (($? >> 8) == 2) { - cleanupcvs(@files); - die "Exiting: Patch reported serious trouble -- you will have to apply this patch manually"; -} elsif (($? >> 8) == 1) { # some hunks failed to apply - $dirtypatch = 1; -} - foreach my $f (@afiles) { - set_new_file_permissions($f, $amodes{$f}); if (grep { $_ eq $f } @bfiles) { system('cvs', 'add','-kb',$f); } else { system('cvs', 'add', $f); } if ($?) { - $dirty = 1; + $dirtypatch = 1; warn "Failed to cvs add $f -- you may need to do it manually"; } } @@ -285,35 +215,40 @@ foreach my $f (@afiles) { foreach my $f (@dfiles) { system('cvs', 'rm', '-f', $f); if ($?) { - $dirty = 1; + $dirtypatch = 1; warn "Failed to cvs rm -f $f -- you may need to do it manually"; } } print "Commit to CVS\n"; -print "Patch: $title\n"; -my $commitfiles = join(' ', @afiles, @mfiles, @dfiles); -my $cmd = "cvs commit -F .msg $commitfiles"; +print "Patch title (first comment line): $title\n"; +my @commitfiles = map { unless (m/\s/) { '\''.$_.'\''; } else { $_; }; } (@files); +my $cmd = "cvs commit -F .msg @commitfiles"; if ($dirtypatch) { print "NOTE: One or more hunks failed to apply cleanly.\n"; - print "Resolve the conflicts and then commit using:\n"; + print "You'll need to apply the patch in .cvsexportcommit.diff manually\n"; + print "using a patch program. After applying the patch and resolving the\n"; + print "problems you may commit using:"; print "\n $cmd\n\n"; exit(1); } - if ($opt_c) { print "Autocommit\n $cmd\n"; - print safe_pipe_capture('cvs', 'commit', '-F', '.msg', @afiles, @mfiles, @dfiles); + print safe_pipe_capture('cvs', 'commit', '-F', '.msg', @files); if ($?) { - cleanupcvs(@files); die "Exiting: The commit did not succeed"; } print "Committed successfully to CVS\n"; } else { print "Ready for you to commit, just run:\n\n $cmd\n"; } + +# clean up +unlink(".cvsexportcommit.diff"); +unlink(".msg"); + sub usage { print STDERR <); + close $child or die join(' ',@_).": $! $?"; + } else { + exec(@_) or die "$! $?"; # exec() can fail the executable can't be found + } + return $output; } diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh index c102479..ca0513b 100755 --- a/t/t9200-git-cvsexportcommit.sh +++ b/t/t9200-git-cvsexportcommit.sh @@ -89,18 +89,17 @@ test_expect_success \ ! git cvsexportcommit -c $id )' -# Should fail, but only on the git-cvsexportcommit stage -test_expect_success \ - 'Fail to remove binary file more than one generation old' \ - 'git reset --hard HEAD^ && - cat F/newfile6.png >>D/newfile4.png && - git commit -a -m "generation 2 (again)" && - rm -f D/newfile4.png && - git commit -a -m "generation 3" && - id=$(git rev-list --max-count=1 HEAD) && - (cd "$CVSWORK" && - ! git cvsexportcommit -c $id - )' +#test_expect_success \ +# 'Fail to remove binary file more than one generation old' \ +# 'git reset --hard HEAD^ && +# cat F/newfile6.png >>D/newfile4.png && +# git commit -a -m "generation 2 (again)" && +# rm -f D/newfile4.png && +# git commit -a -m "generation 3" && +# id=$(git rev-list --max-count=1 HEAD) && +# (cd "$CVSWORK" && +# ! git cvsexportcommit -c $id +# )' # We reuse the state from two tests back here @@ -108,7 +107,7 @@ test_expect_success \ # fail with gnu patch, so cvsexportcommit must handle that. test_expect_success \ 'Remove only binary files' \ - 'git reset --hard HEAD^^^ && + 'git reset --hard HEAD^^ && rm -f D/newfile4.png && git commit -a -m "test: remove only a binary file" && id=$(git rev-list --max-count=1 HEAD) && @@ -142,20 +141,73 @@ test_expect_success \ diff F/newfile6.png ../F/newfile6.png )' -test_expect_success 'Retain execute bit' ' - mkdir G && - echo executeon >G/on && - chmod +x G/on && - echo executeoff >G/off && - git add G/on && - git add G/off && - git commit -a -m "Execute test" && - ( - cd "$CVSWORK" && - git-cvsexportcommit -c HEAD - test -x G/on && - ! test -x G/off - ) -' +test_expect_success \ + 'New file with spaces in file name' \ + 'mkdir "G g" && + echo ok then >"G g/with spaces.txt" && + git add "G g/with spaces.txt" && \ + cp ../test9200a.png "G g/with spaces.png" && \ + git add "G g/with spaces.png" && + git commit -a -m "With spaces" && + id=$(git rev-list --max-count=1 HEAD) && + (cd "$CVSWORK" && + git-cvsexportcommit -c $id && + test "$(echo $(sort "G g/CVS/Entries"|cut -d/ -f2,3,5))" = "with spaces.png/1.1/-kb with spaces.txt/1.1/" + )' + +test_expect_success \ + 'Update file with spaces in file name' \ + 'echo Ok then >>"G g/with spaces.txt" && + cat ../test9200a.png >>"G g/with spaces.png" && \ + git add "G g/with spaces.png" && + git commit -a -m "Update with spaces" && + id=$(git rev-list --max-count=1 HEAD) && + (cd "$CVSWORK" && + git-cvsexportcommit -c $id + test "$(echo $(sort "G g/CVS/Entries"|cut -d/ -f2,3,5))" = "with spaces.png/1.2/-kb with spaces.txt/1.2/" + )' + +# This test contains ISO-8859-1 characters +test_expect_success \ + 'File with non-ascii file name' \ + 'mkdir -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/// && + echo Foo >/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////grdetsgrdet.txt && + git add /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////grdetsgrdet.txt && + cp ../test9200a.png /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////grdetsgrdet.png && + git add /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////grdetsgrdet.png && + git commit -a -m "Gr det s gr det" && \ + id=$(git rev-list --max-count=1 HEAD) && + (cd "$CVSWORK" && + git-cvsexportcommit -v -c $id && + 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))" = "grdetsgrdet.png/1.1/-kb grdetsgrdet.txt/1.1/" + )' + +test_expect_success \ + 'Mismatching patch should fail' \ + 'date >>"E/newfile5.txt" && + git add "E/newfile5.txt" && + git commit -a -m "Update one" && + date >>"E/newfile5.txt" && + git add "E/newfile5.txt" && + git commit -a -m "Update two" && + id=$(git rev-list --max-count=1 HEAD) && + (cd "$CVSWORK" && + ! git-cvsexportcommit -c $id + )' + +test_expect_success \ + 'Retain execute bit' \ + 'mkdir G && + echo executeon >G/on && + chmod +x G/on && + echo executeoff >G/off && + git add G/on && + git add G/off && + git commit -a -m "Execute test" && + (cd "$CVSWORK" && + git-cvsexportcommit -c HEAD + test -x G/on && + ! test -x G/off + )' test_done -- cgit v0.10.2-6-g49f6 From bfddbc5e1e68373ba96441ca228236667912265c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 10 Dec 2006 13:50:59 -0800 Subject: diff --numstat: show binary with '-' to match "apply --numstat" This changes the --numstat output for binary files from "0 0" to "- -" to match what "apply --numstat" does. Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index 3315378..d6d17f4 100644 --- a/diff.c +++ b/diff.c @@ -802,7 +802,10 @@ static void show_numstat(struct diffstat_t* data, struct diff_options *options) for (i = 0; i < data->nr; i++) { struct diffstat_file *file = data->files[i]; - printf("%d\t%d\t", file->added, file->deleted); + if (file->is_binary) + printf("-\t-\t"); + else + printf("%d\t%d\t", file->added, file->deleted); if (options->line_termination && quote_c_style(file->name, NULL, NULL, 0)) quote_c_style(file->name, NULL, stdout, 0); -- cgit v0.10.2-6-g49f6 From 9aa1757382002655cb72bad6a163ff430e08c962 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Mon, 11 Dec 2006 18:09:58 +0100 Subject: gitweb: Don't use Content-Encoding: header in git_snapshot Do not use Content-Encoding: HTTP header in git_snapshot, using instead type according to the snapshot type (compression type). Some of web browser take Content-Encoding: to be _transparent_ also for downloading, and store decompressed file (with incorrect compression suffix) on download. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 5ea3fda..145d5b5 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3414,8 +3414,7 @@ sub git_snapshot { my $filename = basename($project) . "-$hash.tar.$suffix"; print $cgi->header( - -type => 'application/x-tar', - -content_encoding => $ctype, + -type => "application/$ctype", -content_disposition => 'inline; filename="' . "$filename" . '"', -status => '200 OK'); -- cgit v0.10.2-6-g49f6 From e33fba4c5a33bce748d2c8a1c7a38b78a1fd2cda Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 10 Dec 2006 13:25:46 +0100 Subject: gitweb: Show target of symbolic link in "tree" view In "tree" view (git_print_tree_entry subroutine), for entries which are symbolic links, add " -> link_target" after file name (a la "ls -l"). Link target is _not_ hyperlinked. While at it, correct whitespaces (tabs are for aling, spaces are for indent) in modified git_print_tree_entry subroutine. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 145d5b5..b706f74 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -1989,12 +1989,31 @@ sub git_print_log ($;%) { } } +# return link target (what link points to) +sub git_get_link_target { + my $hash = shift; + my $link_target; + + # read link + open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash + or return; + { + local $/; + $link_target = <$fd>; + } + close $fd + or return; + + return $link_target; +} + + # print tree entry (row of git_tree), but without encompassing element sub git_print_tree_entry { my ($t, $basedir, $hash_base, $have_blame) = @_; my %base_key = (); - $base_key{hash_base} = $hash_base if defined $hash_base; + $base_key{'hash_base'} = $hash_base if defined $hash_base; # The format of a table row is: mode list link. Where mode is # the mode of the entry, list is the name of the entry, an href, @@ -2005,16 +2024,23 @@ sub git_print_tree_entry { print "" . $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}", %base_key), - -class => "list"}, esc_path($t->{'name'})) . "\n"; + -class => "list"}, esc_path($t->{'name'})); + if (S_ISLNK(oct $t->{'mode'})) { + my $link_target = git_get_link_target($t->{'hash'}); + if ($link_target) { + print " -> " . esc_path($link_target); + } + } + print "\n"; print ""; print $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key)}, - "blob"); + file_name=>"$basedir$t->{'name'}", %base_key)}, + "blob"); if ($have_blame) { print " | " . $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key)}, - "blame"); + file_name=>"$basedir$t->{'name'}", %base_key)}, + "blame"); } if (defined $hash_base) { print " | " . @@ -2036,8 +2062,8 @@ sub git_print_tree_entry { print "\n"; print ""; print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key)}, - "tree"); + file_name=>"$basedir$t->{'name'}", %base_key)}, + "tree"); if (defined $hash_base) { print " | " . $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, -- cgit v0.10.2-6-g49f6 From ca94601c8fcc4ddda4b48f05c748b3d52c54809c Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 10 Dec 2006 13:25:47 +0100 Subject: gitweb: Add generic git_object subroutine to display object of any type Add generic "object" view implemented in git_object subroutine, which is used to display object of any type; to be more exact it redirects to the view of correct type: "blob", "tree", "commit" or "tag". To identify object you have to provide either hash (identifier of an object), or (in the case of tree and blob objects) hash of commit object (hash_base) and path (file_name). Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index b706f74..d3816b8 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -434,6 +434,7 @@ my %actions = ( "tags" => \&git_tags, "tree" => \&git_tree, "snapshot" => \&git_snapshot, + "object" => \&git_object, # those below don't need $project "opml" => \&git_opml, "project_list" => \&git_project_list, @@ -3619,6 +3620,53 @@ sub git_commit { git_footer_html(); } +sub git_object { + # object is defined by: + # - hash or hash_base alone + # - hash_base and file_name + my $type; + + # - hash or hash_base alone + if ($hash || ($hash_base && !defined $file_name)) { + my $object_id = $hash || $hash_base; + + my $git_command = git_cmd_str(); + open my $fd, "-|", "$git_command cat-file -t $object_id 2>/dev/null" + or die_error('404 Not Found', "Object does not exist"); + $type = <$fd>; + chomp $type; + close $fd + or die_error('404 Not Found', "Object does not exist"); + + # - hash_base and file_name + } elsif ($hash_base && defined $file_name) { + $file_name =~ s,/+$,,; + + system(git_cmd(), "cat-file", '-e', $hash_base) == 0 + or die_error('404 Not Found', "Base object does not exist"); + + # here errors should not hapen + open my $fd, "-|", git_cmd(), "ls-tree", $hash_base, "--", $file_name + or die_error(undef, "Open git-ls-tree failed"); + my $line = <$fd>; + close $fd; + + #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' + unless ($line && $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/) { + die_error('404 Not Found', "File or directory for given base does not exist"); + } + $type = $2; + $hash = $3; + } else { + die_error('404 Not Found', "Not enough information to find object"); + } + + print $cgi->redirect(-uri => href(action=>$type, -full=>1, + hash=>$hash, hash_base=>$hash_base, + file_name=>$file_name), + -status => '302 Found'); +} + sub git_blobdiff { my $format = shift || 'html'; -- cgit v0.10.2-6-g49f6 From 3bf9d57051845cee996f73b5402a07cbeda84c88 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 10 Dec 2006 13:25:48 +0100 Subject: gitweb: Hyperlink target of symbolic link in "tree" view (if possible) Make symbolic link target in "tree" view into hyperlink to generic "object" view (as we don't know if the link target is file (blob) or directory (tree), and if it exist at all). Target of link is made into hyperlink when: * hash_base is provided (otherwise we cannot find hash of link target) * link is relative * in no place link goes out of root tree (top dir) Full path of symlink target from the root dir is provided in the title attribute of hyperlink. Currently symbolic link name uses ordinary file style (hidden hyperlink), while the hyperlink to symlink target uses default hyperlink style, so it is underlined while link target which is not made into hyperlink is not underlined. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index d3816b8..d902913 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2008,6 +2008,48 @@ sub git_get_link_target { return $link_target; } +# given link target, and the directory (basedir) the link is in, +# return target of link relative to top directory (top tree); +# return undef if it is not possible (including absolute links). +sub normalize_link_target { + my ($link_target, $basedir, $hash_base) = @_; + + # we can normalize symlink target only if $hash_base is provided + return unless $hash_base; + + # absolute symlinks (beginning with '/') cannot be normalized + return if (substr($link_target, 0, 1) eq '/'); + + # normalize link target to path from top (root) tree (dir) + my $path; + if ($basedir) { + $path = $basedir . '/' . $link_target; + } else { + # we are in top (root) tree (dir) + $path = $link_target; + } + + # remove //, /./, and /../ + my @path_parts; + foreach my $part (split('/', $path)) { + # discard '.' and '' + next if (!$part || $part eq '.'); + # handle '..' + if ($part eq '..') { + if (@path_parts) { + pop @path_parts; + } else { + # link leads outside repository (outside top dir) + return; + } + } else { + push @path_parts, $part; + } + } + $path = join('/', @path_parts); + + return $path; +} # print tree entry (row of git_tree), but without encompassing element sub git_print_tree_entry { @@ -2029,7 +2071,15 @@ sub git_print_tree_entry { if (S_ISLNK(oct $t->{'mode'})) { my $link_target = git_get_link_target($t->{'hash'}); if ($link_target) { - print " -> " . esc_path($link_target); + my $norm_target = normalize_link_target($link_target, $basedir, $hash_base); + if (defined $norm_target) { + print " -> " . + $cgi->a({-href => href(action=>"object", hash_base=>$hash_base, + file_name=>$norm_target), + -title => $norm_target}, esc_path($link_target)); + } else { + print " -> " . esc_path($link_target); + } } } print "\n"; -- cgit v0.10.2-6-g49f6 From bfe2191f79aa4e74eafc709ea41cc0999c9f5be5 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 10 Dec 2006 13:25:49 +0100 Subject: gitweb: SHA-1 in commit log message links to "object" view Instead of checking if explicit SHA-1 in commit log message is sha1 of commit and making link to "commit" view, make [fragment of] explicit SHA-1 in commit log message link to "object" view. While at it allow to hyperlink also shortened SHA-1, from 8 characters up to full SHA-1, instead of requiring full 40 characters of SHA-1. This makes the following changes: * SHA-1 of objects which no longer exists, for example in commit cherry-picked from no longer existing temporary branch, or revert of commit in rebased branch, are no longer marked as such by not being made into hyperlink (and not having default hyperlink view: being underlined among others). On the other hand it makes gitweb to not write error messages when object is not found to web serwer log; it also moves cost of getting type and SHA-1 validation to when link is clicked, and not only viewed. * SHA-1 of other objects: blobs, trees, tags are also hyperlinked and lead to appropriate view (although in the case of tags it is more natural to just use tag name). * You can put shortened SHA-1 of commit in the commit message, and it would be hyperlinked; it would be checked on clicking if abbrev is unique. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index d902913..040ee71 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -828,14 +828,12 @@ sub format_log_line_html { my $line = shift; $line = esc_html($line, -nbsp=>1); - if ($line =~ m/([0-9a-fA-F]{40})/) { + if ($line =~ m/([0-9a-fA-F]{8,40})/) { my $hash_text = $1; - if (git_get_type($hash_text) eq "commit") { - my $link = - $cgi->a({-href => href(action=>"commit", hash=>$hash_text), - -class => "text"}, $hash_text); - $line =~ s/$hash_text/$link/; - } + my $link = + $cgi->a({-href => href(action=>"object", hash=>$hash_text), + -class => "text"}, $hash_text); + $line =~ s/$hash_text/$link/; } return $line; } -- cgit v0.10.2-6-g49f6 From a1158caeadec4d48b185068f4120d85423de5970 Mon Sep 17 00:00:00 2001 From: Andy Parkins Date: Tue, 12 Dec 2006 06:41:52 +0000 Subject: Colourise git-branch output I wanted to have a visual indication of which branches are local and which are remote in git-branch -a output; however Junio was concerned that someone might be using the output in a script. This patch addresses the problem by colouring the git-branch output - which in "auto" mode won't be activated. I've based it off the colouring code for builtin-diff.c; which means there is a branch color configuration variable that needs setting to something before the color will appear. The colour parameter is "color.branch" rather than "branch.color" to avoid clashing with the default namespace for default branch merge definitions. This patch chooses green for local, red for remote and bold green for current. Signed-off-by: Andy Parkins Signed-off-by: Junio C Hamano diff --git a/builtin-branch.c b/builtin-branch.c index 3d5cb0e..7c87b8d 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -5,6 +5,7 @@ * Based on git-branch.sh by Junio C Hamano. */ +#include "color.h" #include "cache.h" #include "refs.h" #include "commit.h" @@ -17,6 +18,58 @@ static const char builtin_branch_usage[] = static const char *head; static unsigned char head_sha1[20]; +static int branch_use_color; +static char branch_colors[][COLOR_MAXLEN] = { + "\033[m", /* reset */ + "", /* PLAIN (normal) */ + "\033[31m", /* REMOTE (red) */ + "\033[32m", /* LOCAL (green) */ + "\033[1;32m", /* CURRENT (boldgreen) */ +}; +enum color_branch { + COLOR_BRANCH_RESET = 0, + COLOR_BRANCH_PLAIN = 1, + COLOR_BRANCH_REMOTE = 2, + COLOR_BRANCH_LOCAL = 3, + COLOR_BRANCH_CURRENT = 4, +}; + +static int parse_branch_color_slot(const char *var, int ofs) +{ + if (!strcasecmp(var+ofs, "plain")) + return COLOR_BRANCH_PLAIN; + if (!strcasecmp(var+ofs, "reset")) + return COLOR_BRANCH_RESET; + if (!strcasecmp(var+ofs, "remote")) + return COLOR_BRANCH_REMOTE; + if (!strcasecmp(var+ofs, "local")) + return COLOR_BRANCH_LOCAL; + if (!strcasecmp(var+ofs, "current")) + return COLOR_BRANCH_CURRENT; + die("bad config variable '%s'", var); +} + +int git_branch_config(const char *var, const char *value) +{ + if (!strcmp(var, "color.branch")) { + branch_use_color = git_config_colorbool(var, value); + return 0; + } + if (!strncmp(var, "color.branch.", 13)) { + int slot = parse_branch_color_slot(var, 13); + color_parse(value, var, branch_colors[slot]); + return 0; + } + return git_default_config(var, value); +} + +const char *branch_get_color(enum color_branch ix) +{ + if (branch_use_color) + return branch_colors[ix]; + return ""; +} + static int in_merge_bases(const unsigned char *sha1, struct commit *rev1, struct commit *rev2) @@ -183,6 +236,7 @@ static void print_ref_list(int kinds, int verbose, int abbrev) int i; char c; struct ref_list ref_list; + int color; memset(&ref_list, 0, sizeof(ref_list)); ref_list.kinds = kinds; @@ -191,18 +245,38 @@ static void print_ref_list(int kinds, int verbose, int abbrev) qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp); for (i = 0; i < ref_list.index; i++) { + switch( ref_list.list[i].kind ) { + case REF_LOCAL_BRANCH: + color = COLOR_BRANCH_LOCAL; + break; + case REF_REMOTE_BRANCH: + color = COLOR_BRANCH_REMOTE; + break; + default: + color = COLOR_BRANCH_PLAIN; + break; + } + c = ' '; if (ref_list.list[i].kind == REF_LOCAL_BRANCH && - !strcmp(ref_list.list[i].name, head)) + !strcmp(ref_list.list[i].name, head)) { c = '*'; + color = COLOR_BRANCH_CURRENT; + } if (verbose) { - printf("%c %-*s", c, ref_list.maxwidth, - ref_list.list[i].name); + printf("%c %s%-*s%s", c, + branch_get_color(color), + ref_list.maxwidth, + ref_list.list[i].name, + branch_get_color(COLOR_BRANCH_RESET)); print_ref_info(ref_list.list[i].sha1, abbrev); } else - printf("%c %s\n", c, ref_list.list[i].name); + printf("%c %s%s%s\n", c, + branch_get_color(color), + ref_list.list[i].name, + branch_get_color(COLOR_BRANCH_RESET)); } free_ref_list(&ref_list); @@ -253,7 +327,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) int kinds = REF_LOCAL_BRANCH; int i; - git_config(git_default_config); + git_config(git_branch_config); for (i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -297,6 +371,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix) verbose = 1; continue; } + if (!strcmp(arg, "--color")) { + branch_use_color = 1; + continue; + } + if (!strcmp(arg, "--no-color")) { + branch_use_color = 0; + continue; + } usage(builtin_branch_usage); } -- cgit v0.10.2-6-g49f6 From 59f867400650b39568e4a7f96bd60f3a0072dbda Mon Sep 17 00:00:00 2001 From: Brian Gernhardt Date: Tue, 12 Dec 2006 12:01:47 -0500 Subject: Move Fink and Ports check to after config file Putting NO_FINK or NO_DARWIN_PORTS in config.mak is ignored because the checks are done before the config is included. Signed-off-by: Brian Gernhardt Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 36ce8cd..c5a1804 100644 --- a/Makefile +++ b/Makefile @@ -334,18 +334,6 @@ ifeq ($(uname_S),Darwin) NEEDS_SSL_WITH_CRYPTO = YesPlease NEEDS_LIBICONV = YesPlease NO_STRLCPY = YesPlease - ifndef NO_FINK - ifeq ($(shell test -d /sw/lib && echo y),y) - BASIC_CFLAGS += -I/sw/include - BASIC_LDFLAGS += -L/sw/lib - endif - endif - ifndef NO_DARWIN_PORTS - ifeq ($(shell test -d /opt/local/lib && echo y),y) - BASIC_CFLAGS += -I/opt/local/include - BASIC_LDFLAGS += -L/opt/local/lib - endif - endif endif ifeq ($(uname_S),SunOS) NEEDS_SOCKET = YesPlease @@ -423,6 +411,21 @@ endif -include config.mak.autogen -include config.mak +ifeq ($(uname_S),Darwin) + ifndef NO_FINK + ifeq ($(shell test -d /sw/lib && echo y),y) + BASIC_CFLAGS += -I/sw/include + BASIC_LDFLAGS += -L/sw/lib + endif + endif + ifndef NO_DARWIN_PORTS + ifeq ($(shell test -d /opt/local/lib && echo y),y) + BASIC_CFLAGS += -I/opt/local/include + BASIC_LDFLAGS += -L/opt/local/lib + endif + endif +endif + ifdef WITH_OWN_SUBPROCESS_PY PYMODULES += compat/subprocess.py else -- cgit v0.10.2-6-g49f6 From 0d7a6e4ef9e2dc458a9a56ab73638d97f4e75d87 Mon Sep 17 00:00:00 2001 From: Alex Riesen Date: Tue, 12 Dec 2006 18:34:02 +0100 Subject: Clarify fetch error for missing objects. Otherwise there're such things like: Cannot obtain needed none 9a6e87b60dbd2305c95cecce7d9d60f849a0658d while processing commit 0000000000000000000000000000000000000000. which while looks weird. What is the none needed for? Signed-off-by: Junio C Hamano diff --git a/fetch.c b/fetch.c index c426c04..663b4b2 100644 --- a/fetch.c +++ b/fetch.c @@ -22,14 +22,15 @@ void pull_say(const char *fmt, const char *hex) fprintf(stderr, fmt, hex); } -static void report_missing(const char *what, const unsigned char *missing) +static void report_missing(const struct object *obj) { char missing_hex[41]; - - strcpy(missing_hex, sha1_to_hex(missing));; - fprintf(stderr, - "Cannot obtain needed %s %s\nwhile processing commit %s.\n", - what, missing_hex, sha1_to_hex(current_commit_sha1)); + strcpy(missing_hex, sha1_to_hex(obj->sha1));; + fprintf(stderr, "Cannot obtain needed %s %s\n", + obj->type ? typename(obj->type): "object", missing_hex); + if (!is_null_sha1(current_commit_sha1)) + fprintf(stderr, "while processing commit %s.\n", + sha1_to_hex(current_commit_sha1)); } static int process(struct object *obj); @@ -177,7 +178,7 @@ static int loop(void) */ if (! (obj->flags & TO_SCAN)) { if (fetch(obj->sha1)) { - report_missing(typename(obj->type), obj->sha1); + report_missing(obj); return -1; } } -- cgit v0.10.2-6-g49f6 From d2a9a87b8a98e3b3797c6c1e5aa2269f36c2b47a Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 12 Dec 2006 14:47:00 -0800 Subject: git-svn: enable logging of information not supported by git The changes are now tracked in $GIT_DIR/svn/$GIT_SVN_ID/untracked.log Information in the untracked.log include: * the addition and removal of empty directories (changes of these will also warn the user) * file and directory property changes, including (but not limited to) svk:merge and svn:externals * revision properties (revprops) are also tracked * users will be warned of 'absent' file and directories (if users are forbidden access) Fields in entries are separated by spaces; "unsafe" characters are URI-encoded so that each entry takes exactly one line. There is currently no automated parser for dealing with the data in untracked.log, but it should be possible to write one to create empty directories on checkout and manage externals/subprojects. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index 1f8a3b0..06e89ff 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -21,6 +21,16 @@ $ENV{TZ} = 'UTC'; $ENV{LC_ALL} = 'C'; $| = 1; # unbuffer STDOUT +# properties that we do not log: +my %SKIP = ( 'svn:wc:ra_dav:version-url' => 1, + 'svn:special' => 1, + 'svn:executable' => 1, + 'svn:entry:committed-rev' => 1, + 'svn:entry:last-author' => 1, + 'svn:entry:uuid' => 1, + 'svn:entry:committed-date' => 1, +); + sub fatal (@) { print STDERR $@; exit 1 } # If SVN:: library support is added, please make the dependencies # optional and preserve the capability to use the command-line client. @@ -2902,7 +2912,7 @@ sub libsvn_dup_ra { } sub libsvn_get_file { - my ($gui, $f, $rev, $chg) = @_; + my ($gui, $f, $rev, $chg, $untracked) = @_; $f =~ s#^/##; print "\t$chg\t$f\n" unless $_q; @@ -2940,11 +2950,25 @@ sub libsvn_get_file { waitpid $pid, 0; $hash =~ /^$sha1$/o or die "not a sha1: $hash\n"; } + %{$untracked->{file_prop}->{$f}} = %$props; print $gui $mode,' ',$hash,"\t",$f,"\0" or croak $!; } +sub uri_encode { + my ($f) = @_; + $f =~ s#([^a-zA-Z0-9\*!\:_\./\-])#uc sprintf("%%%02x",ord($1))#eg; + $f +} + +sub uri_decode { + my ($f) = @_; + $f =~ tr/+/ /; + $f =~ s/%([A-F0-9]{2})/chr hex($1)/ge; + $f +} + sub libsvn_log_entry { - my ($rev, $author, $date, $msg, $parents) = @_; + my ($rev, $author, $date, $msg, $parents, $untracked) = @_; my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T (\d\d)\:(\d\d)\:(\d\d).\d+Z$/x) or die "Unable to parse date: $date\n"; @@ -2952,8 +2976,65 @@ sub libsvn_log_entry { die "Author: $author not defined in $_authors file\n"; } $msg = '' if ($rev == 0 && !defined $msg); - return { revision => $rev, date => "+0000 $Y-$m-$d $H:$M:$S", - author => $author, msg => $msg."\n", parents => $parents || [] } + + open my $un, '>>', "$GIT_SVN_DIR/unhandled.log" or croak $!; + my $h; + print $un "r$rev\n" or croak $!; + $h = $untracked->{empty}; + foreach (sort keys %$h) { + my $act = $h->{$_} ? '+empty_dir' : '-empty_dir'; + print $un " $act: ", uri_encode($_), "\n" or croak $!; + warn "W: $act: $_\n"; + } + foreach my $t (qw/dir_prop file_prop/) { + $h = $untracked->{$t} or next; + foreach my $path (sort keys %$h) { + my $ppath = $path eq '' ? '.' : $path; + foreach my $prop (sort keys %{$h->{$path}}) { + next if $SKIP{$prop}; + my $v = $h->{$path}->{$prop}; + if (defined $v) { + print $un " +$t: ", + uri_encode($ppath), ' ', + uri_encode($prop), ' ', + uri_encode($v), "\n" + or croak $!; + } else { + print $un " -$t: ", + uri_encode($ppath), ' ', + uri_encode($prop), "\n" + or croak $!; + } + } + } + } + foreach my $t (qw/absent_file absent_directory/) { + $h = $untracked->{$t} or next; + foreach my $parent (sort keys %$h) { + foreach my $path (sort @{$h->{$parent}}) { + print $un " $t: ", + uri_encode("$parent/$path"), "\n" + or croak $!; + warn "W: $t: $parent/$path ", + "Insufficient permissions?\n"; + } + } + } + + # revprops (make this optional? it's an extra network trip...) + my $pool = SVN::Pool->new; + my $rp = $SVN->rev_proplist($rev, $pool); + foreach (sort keys %$rp) { + next if /^svn:(?:author|date|log)$/; + print $un " rev_prop: ", uri_encode($_), ' ', + uri_encode($rp->{$_}), "\n"; + } + $pool->clear; + close $un or croak $!; + + { revision => $rev, date => "+0000 $Y-$m-$d $H:$M:$S", + author => $author, msg => $msg."\n", parents => $parents || [], + revprops => $rp } } sub process_rm { @@ -2972,9 +3053,11 @@ sub process_rm { } print "\tD\t$f/\n" unless $q; close $ls or croak $?; + return $SVN::Node::dir; } else { print $gui '0 ',0 x 40,"\t",$f,"\0" or croak $!; print "\tD\t$f\n" unless $q; + return $SVN::Node::file; } } @@ -2995,13 +3078,14 @@ sub libsvn_fetch_delta { unless ($ed->{git_commit_ok}) { die "SVN connection failed somewhere...\n"; } - libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]); + libsvn_log_entry($rev, $author, $date, $msg, [$last_commit], $ed); } sub libsvn_fetch_full { my ($last_commit, $paths, $rev, $author, $date, $msg) = @_; open my $gui, '| git-update-index -z --index-info' or croak $!; my %amr; + my $ut = { empty => {}, dir_prop => {}, file_prop => {} }; my $p = $SVN->{svn_path}; foreach my $f (keys %$paths) { my $m = $paths->{$f}->action(); @@ -3012,8 +3096,11 @@ sub libsvn_fetch_full { $f =~ s#^/##; } if ($m =~ /^[DR]$/) { - process_rm($gui, $last_commit, $f, $_q); - next if $m eq 'D'; + my $t = process_rm($gui, $last_commit, $f, $_q); + if ($m eq 'D') { + $ut->{empty}->{$f} = 0 if $t == $SVN::Node::dir; + next; + } # 'R' can be file replacements, too, right? } my $pool = SVN::Pool->new; @@ -3026,18 +3113,32 @@ sub libsvn_fetch_full { } } elsif ($t == $SVN::Node::dir && $m =~ /^[AR]$/) { my @traversed = (); - libsvn_traverse($gui, '', $f, $rev, \@traversed); - foreach (@traversed) { - $amr{$_} = $m; + libsvn_traverse($gui, '', $f, $rev, \@traversed, $ut); + if (@traversed) { + foreach (@traversed) { + $amr{$_} = $m; + } + } else { + my ($dir, $file) = ($f =~ m#^(.*?)/?([^/]+)$#); + delete $ut->{empty}->{$dir}; + $ut->{empty}->{$f} = 1; } } $pool->clear; } foreach (keys %amr) { - libsvn_get_file($gui, $_, $rev, $amr{$_}); + libsvn_get_file($gui, $_, $rev, $amr{$_}, $ut); + my ($d) = ($_ =~ m#^(.*?)/?(?:[^/]+)$#); + delete $ut->{empty}->{$d}; + } + unless (exists $ut->{dir_prop}->{''}) { + my $pool = SVN::Pool->new; + my (undef, undef, $props) = $SVN->get_dir('', $rev, $pool); + %{$ut->{dir_prop}->{''}} = %$props; + $pool->clear; } close $gui or croak $?; - return libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]); + libsvn_log_entry($rev, $author, $date, $msg, [$last_commit], $ut); } sub svn_grab_base_rev { @@ -3098,25 +3199,38 @@ sub libsvn_parse_revision { } sub libsvn_traverse { - my ($gui, $pfx, $path, $rev, $files) = @_; + my ($gui, $pfx, $path, $rev, $files, $untracked) = @_; my $cwd = length $pfx ? "$pfx/$path" : $path; my $pool = SVN::Pool->new; $cwd =~ s#^\Q$SVN->{svn_path}\E##; + my $nr = 0; my ($dirent, $r, $props) = $SVN->get_dir($cwd, $rev, $pool); + %{$untracked->{dir_prop}->{$cwd}} = %$props; foreach my $d (keys %$dirent) { my $t = $dirent->{$d}->kind; if ($t == $SVN::Node::dir) { - libsvn_traverse($gui, $cwd, $d, $rev, $files); + my $i = libsvn_traverse($gui, $cwd, $d, $rev, + $files, $untracked); + if ($i) { + $nr += $i; + } else { + $untracked->{empty}->{"$cwd/$d"} = 1; + } } elsif ($t == $SVN::Node::file) { + $nr++; my $file = "$cwd/$d"; if (defined $files) { push @$files, $file; } else { - libsvn_get_file($gui, $file, $rev, 'A'); + libsvn_get_file($gui, $file, $rev, 'A', + $untracked); + my ($dir) = ($file =~ m#^(.*?)/?(?:[^/]+)$#); + delete $untracked->{empty}->{$dir}; } } } $pool->clear; + $nr; } sub libsvn_traverse_ignore { @@ -3255,6 +3369,7 @@ sub libsvn_new_tree { return $log_entry; } my ($paths, $rev, $author, $date, $msg) = @_; + my $ut; if ($_xfer_delta) { my $pool = SVN::Pool->new; my $ed = SVN::Git::Fetcher->new({q => $_q}); @@ -3266,12 +3381,14 @@ sub libsvn_new_tree { unless ($ed->{git_commit_ok}) { die "SVN connection failed somewhere...\n"; } + $ut = $ed; } else { + $ut = { empty => {}, dir_prop => {}, file_prop => {} }; open my $gui, '| git-update-index -z --index-info' or croak $!; - libsvn_traverse($gui, '', $SVN->{svn_path}, $rev); + libsvn_traverse($gui, '', $SVN->{svn_path}, $rev, undef, $ut); close $gui or croak $?; } - return libsvn_log_entry($rev, $author, $date, $msg); + libsvn_log_entry($rev, $author, $date, $msg, [], $ut); } sub find_graft_path_commit { @@ -3456,13 +3573,28 @@ sub new { $self->{gui} = $gui; $self->{c} = $git_svn->{c} if exists $git_svn->{c}; $self->{q} = $git_svn->{q}; + $self->{empty} = {}; + $self->{dir_prop} = {}; + $self->{file_prop} = {}; + $self->{absent_dir} = {}; + $self->{absent_file} = {}; require Digest::MD5; $self; } +sub open_root { + { path => '' }; +} + +sub open_directory { + my ($self, $path, $pb, $rev) = @_; + { path => $path }; +} + sub delete_entry { my ($self, $path, $rev, $pb) = @_; - process_rm($self->{gui}, $self->{c}, $path, $self->{q}); + my $t = process_rm($self->{gui}, $self->{c}, $path, $self->{q}); + $self->{empty}->{$path} = 0 if $t == $SVN::Node::dir; undef; } @@ -3479,10 +3611,41 @@ sub open_file { sub add_file { my ($self, $path, $pb, $cp_path, $cp_rev) = @_; + my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#); + delete $self->{empty}->{$dir}; { path => $path, mode_a => 100644, mode_b => 100644, pool => SVN::Pool->new, action => 'A' }; } +sub add_directory { + my ($self, $path, $cp_path, $cp_rev) = @_; + my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#); + delete $self->{empty}->{$dir}; + $self->{empty}->{$path} = 1; + { path => $path }; +} + +sub change_dir_prop { + my ($self, $db, $prop, $value) = @_; + $self->{dir_prop}->{$db->{path}} ||= {}; + $self->{dir_prop}->{$db->{path}}->{$prop} = $value; + undef; +} + +sub absent_directory { + my ($self, $path, $pb) = @_; + $self->{absent_dir}->{$pb->{path}} ||= []; + push @{$self->{absent_dir}->{$pb->{path}}}, $path; + undef; +} + +sub absent_file { + my ($self, $path, $pb) = @_; + $self->{absent_file}->{$pb->{path}} ||= []; + push @{$self->{absent_file}->{$pb->{path}}}, $path; + undef; +} + sub change_file_prop { my ($self, $fb, $prop, $value) = @_; if ($prop eq 'svn:executable') { @@ -3491,6 +3654,9 @@ sub change_file_prop { } } elsif ($prop eq 'svn:special') { $fb->{mode_b} = defined $value ? 120000 : 100644; + } else { + $self->{file_prop}->{$fb->{path}} ||= {}; + $self->{file_prop}->{$fb->{path}}->{$prop} = $value; } undef; } -- cgit v0.10.2-6-g49f6 From dd31da2fdc199132c9fd42023aea5b33672d73cc Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 12 Dec 2006 14:47:01 -0800 Subject: git-svn: allow dcommit to take an alternate head Previously dcommit would unconditionally commit all patches up-to and including the current HEAD. Now if an optional command-line argument is specified, it will only commit up to the specified revision. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index a45067e..c589a98 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -57,11 +57,13 @@ See '<>' if you are interested in manually joining branches on commit. 'dcommit':: - Commit all diffs from the current HEAD directly to the SVN + Commit all diffs from a specified head directly to the SVN repository, and then rebase or reset (depending on whether or - not there is a diff between SVN and HEAD). It is recommended + not there is a diff between SVN and head). It is recommended that you run git-svn fetch and rebase (not pull) your commits against the latest changes in the SVN repository. + An optional command-line argument may be specified as an + alternative to HEAD. This is advantageous over 'commit' (below) because it produces cleaner, more linear history. diff --git a/git-svn.perl b/git-svn.perl index 06e89ff..819584b 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -604,8 +604,9 @@ sub commit_lib { } sub dcommit { + my $head = shift || 'HEAD'; my $gs = "refs/remotes/$GIT_SVN"; - chomp(my @refs = safe_qx(qw/git-rev-list --no-merges/, "$gs..HEAD")); + chomp(my @refs = safe_qx(qw/git-rev-list --no-merges/, "$gs..$head")); my $last_rev; foreach my $d (reverse @refs) { if (quiet_run('git-rev-parse','--verify',"$d~1") != 0) { @@ -632,16 +633,16 @@ sub dcommit { } return if $_dry_run; fetch(); - my @diff = safe_qx(qw/git-diff-tree HEAD/, $gs); + my @diff = safe_qx('git-diff-tree', $head, $gs); my @finish; if (@diff) { @finish = qw/rebase/; push @finish, qw/--merge/ if $_merge; push @finish, "--strategy=$_strategy" if $_strategy; - print STDERR "W: HEAD and $gs differ, using @finish:\n", @diff; + print STDERR "W: $head and $gs differ, using @finish:\n", @diff; } else { - print "No changes between current HEAD and $gs\n", - "Hard resetting to the latest $gs\n"; + print "No changes between current $head and $gs\n", + "Resetting to the latest $gs\n"; @finish = qw/reset --mixed/; } sys('git', @finish, $gs); -- cgit v0.10.2-6-g49f6 From 6fda05aebe6e36bfe87113f85b6e70f2b9b73e42 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 12 Dec 2006 14:47:02 -0800 Subject: git-svn: correctly display fatal() error messages If I wanted to print $@, I'd pass $@ to fatal(). This looks like a stupid typo on my part. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index 819584b..c746a3c 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -31,7 +31,7 @@ my %SKIP = ( 'svn:wc:ra_dav:version-url' => 1, 'svn:entry:committed-date' => 1, ); -sub fatal (@) { print STDERR $@; exit 1 } +sub fatal (@) { print STDERR @_; exit 1 } # If SVN:: library support is added, please make the dependencies # optional and preserve the capability to use the command-line client. # use eval { require SVN::... } to make it lazy load -- cgit v0.10.2-6-g49f6 From c93be3b539da06e2d89d613448dfadf83c48de53 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 12 Dec 2006 16:36:16 -0800 Subject: add test case for recursive merge This test case is based on the bug report by Shawn Pearce. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/t/t6024-recursive-merge.sh b/t/t6024-recursive-merge.sh new file mode 100755 index 0000000..9416c27 --- /dev/null +++ b/t/t6024-recursive-merge.sh @@ -0,0 +1,70 @@ +#!/bin/sh + +test_description='Test merge without common ancestors' +. ./test-lib.sh + +# This scenario is based on a real-world repository of Shawn Pearce. + +# 1 - A - D - F +# \ X / +# B X +# X \ +# 2 - C - E - G + +export GIT_COMMITTER_DATE="2006-12-12 23:28:00 +0100" +echo 1 > a1 +git add a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:00" git commit -m 1 a1 + +git checkout -b A master +echo A > a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:01" git commit -m A a1 + +git checkout -b B master +echo B > a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:02" git commit -m B a1 + +git checkout -b D A +git-rev-parse B > .git/MERGE_HEAD +echo D > a1 +git update-index a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:03" git commit -m D + +git symbolic-ref HEAD refs/heads/other +echo 2 > a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:04" git commit -m 2 a1 + +git checkout -b C +echo C > a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:05" git commit -m C a1 + +git checkout -b E C +git-rev-parse B > .git/MERGE_HEAD +echo E > a1 +git update-index a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:06" git commit -m E + +git checkout -b G E +git-rev-parse A > .git/MERGE_HEAD +echo G > a1 +git update-index a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:07" git commit -m G + +git checkout -b F D +git-rev-parse C > .git/MERGE_HEAD +echo F > a1 +git update-index a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F + +test_expect_failure "combined merge conflicts" "git merge -m final G" + +git ls-files --stage > out +cat > expect << EOF +100644 f70f10e4db19068f79bc43844b49f3eece45c4e8 1 a1 +100644 cf84443e49e1b366fac938711ddf4be2d4d1d9e9 2 a1 +100644 fd7923529855d0b274795ae3349c5e0438333979 3 a1 +EOF + +test_expect_success "virtual trees were processed" "diff -u expect out" + +test_done -- cgit v0.10.2-6-g49f6 From c53d696bcc2894b0df277e617740b15bac794df9 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 12 Dec 2006 16:45:00 -0800 Subject: git-svn: correctly handle packed-refs in refs/remotes/ We now use git-rev-parse universally to read refs, instead of our own file_to_s function (which I plan on removing). Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index c746a3c..15254e4 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2027,9 +2027,17 @@ sub git_commit { # just in case we clobber the existing ref, we still want that ref # as our parent: - if (my $cur = eval { file_to_s("$GIT_DIR/refs/remotes/$GIT_SVN") }) { + open my $null, '>', '/dev/null' or croak $!; + open my $stderr, '>&', \*STDERR or croak $!; + open STDERR, '>&', $null or croak $!; + if (my $cur = eval { safe_qx('git-rev-parse', + "refs/remotes/$GIT_SVN^0") }) { + chomp $cur; push @tmp_parents, $cur; } + open STDERR, '>&', $stderr or croak $!; + close $stderr or croak $!; + close $null or croak $!; if (exists $tree_map{$tree}) { foreach my $p (@{$tree_map{$tree}}) { -- cgit v0.10.2-6-g49f6 From e2b7008752d85874919ea718d098fec01b4a9019 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Dec 2006 00:01:41 +0100 Subject: Get rid of the dependency on RCS' merge program Now that we have git-merge-file, an RCS merge lookalike, we no longer need it. So long, merge, and thanks for all the fish! Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/.gitignore b/.gitignore index 7f2cd55..d706dd9 100644 --- a/.gitignore +++ b/.gitignore @@ -60,6 +60,7 @@ git-mailsplit git-merge git-merge-base git-merge-index +git-merge-file git-merge-tree git-merge-octopus git-merge-one-file diff --git a/Documentation/git-merge-index.txt b/Documentation/git-merge-index.txt index 6cd0601..0cf505e 100644 --- a/Documentation/git-merge-index.txt +++ b/Documentation/git-merge-index.txt @@ -40,8 +40,8 @@ If "git-merge-index" is called with multiple s (or -a) then it processes them in turn only stopping if merge returns a non-zero exit code. -Typically this is run with the a script calling the merge command from -the RCS package. +Typically this is run with the a script calling git's imitation of +the merge command from the RCS package. A sample script called "git-merge-one-file" is included in the distribution. diff --git a/INSTALL b/INSTALL index 8f69039..b5dd9f0 100644 --- a/INSTALL +++ b/INSTALL @@ -82,15 +82,6 @@ Issues of note: do that even if it wasn't for git. There's no point in living in the dark ages any more. - - "merge", the standard UNIX three-way merge program. It usually - comes with the "rcs" package on most Linux distributions, so if - you have a developer install you probably have it already, but a - "graphical user desktop" install might have left it out. - - You'll only need the merge program if you do development using - git, and if you only use git to track other peoples work you'll - never notice the lack of it. - - "wish", the Tcl/Tk windowing shell is used in gitk to show the history graphically diff --git a/git-cvsserver.perl b/git-cvsserver.perl index ca519b7..55ff83b 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -945,7 +945,7 @@ sub req_update $log->debug("Temporary directory for merge is $dir"); - my $return = system("merge", $file_local, $file_old, $file_new); + my $return = system("git merge-file", $file_local, $file_old, $file_new); $return >>= 8; if ( $return == 0 ) diff --git a/git-rerere.perl b/git-rerere.perl index d3664ff..2e8dbbd 100755 --- a/git-rerere.perl +++ b/git-rerere.perl @@ -154,7 +154,7 @@ sub find_conflict { sub merge { my ($name, $path) = @_; record_preimage($path, "$rr_dir/$name/thisimage"); - unless (system('merge', map { "$rr_dir/$name/${_}image" } + unless (system('git merge-file', map { "$rr_dir/$name/${_}image" } qw(this pre post))) { my $in; open $in, "<$rr_dir/$name/thisimage" or diff --git a/git.spec.in b/git.spec.in index f2374b7..fb95e37 100644 --- a/git.spec.in +++ b/git.spec.in @@ -24,7 +24,7 @@ This is a dummy package which brings in all subpackages. %package core Summary: Core git tools Group: Development/Tools -Requires: zlib >= 1.2, rsync, rcs, curl, less, openssh-clients, expat +Requires: zlib >= 1.2, rsync, curl, less, openssh-clients, expat %description core This is a stupid (but extremely fast) directory content manager. It doesn't do a whole lot, but what it _does_ do is track directory diff --git a/merge-file.c b/merge-file.c index fc9b148..69dc1eb 100644 --- a/merge-file.c +++ b/merge-file.c @@ -3,52 +3,6 @@ #include "xdiff-interface.h" #include "blob.h" -static void rm_temp_file(const char *filename) -{ - unlink(filename); - free((void *)filename); -} - -static const char *write_temp_file(mmfile_t *f) -{ - int fd; - const char *tmp = getenv("TMPDIR"); - char *filename; - - if (!tmp) - tmp = "/tmp"; - filename = mkpath("%s/%s", tmp, "git-tmp-XXXXXX"); - fd = mkstemp(filename); - if (fd < 0) - return NULL; - filename = xstrdup(filename); - if (f->size != xwrite(fd, f->ptr, f->size)) { - rm_temp_file(filename); - return NULL; - } - close(fd); - return filename; -} - -static void *read_temp_file(const char *filename, unsigned long *size) -{ - struct stat st; - char *buf = NULL; - int fd = open(filename, O_RDONLY); - if (fd < 0) - return NULL; - if (!fstat(fd, &st)) { - *size = st.st_size; - buf = xmalloc(st.st_size); - if (st.st_size != xread(fd, buf, st.st_size)) { - free(buf); - buf = NULL; - } - } - close(fd); - return buf; -} - static int fill_mmfile_blob(mmfile_t *f, struct blob *obj) { void *buf; @@ -72,22 +26,19 @@ static void free_mmfile(mmfile_t *f) static void *three_way_filemerge(mmfile_t *base, mmfile_t *our, mmfile_t *their, unsigned long *size) { - void *res; - const char *t1, *t2, *t3; - - t1 = write_temp_file(base); - t2 = write_temp_file(our); - t3 = write_temp_file(their); - res = NULL; - if (t1 && t2 && t3) { - int code = run_command("merge", t2, t1, t3, NULL); - if (!code || code == -1) - res = read_temp_file(t2, size); - } - rm_temp_file(t1); - rm_temp_file(t2); - rm_temp_file(t3); - return res; + mmbuffer_t res; + xpparam_t xpp; + int merge_status; + + memset(&xpp, 0, sizeof(xpp)); + merge_status = xdl_merge(base, our, ".our", their, ".their", + &xpp, XDL_MERGE_ZEALOUS, &res); + + if (merge_status < 0) + return NULL; + + *size = res.size; + return res.ptr; } static int common_outf(void *priv_, mmbuffer_t *mb, int nbuf) diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 81f3bed..3260d1d 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -19,11 +19,7 @@ modification *should* take notice and update the test vectors here. ' ################################################################ -# It appears that people are getting bitten by not installing -# 'merge' (usually part of RCS package in binary distributions). -# Check this and error out before running any tests. Also catch -# the bogosity of trying to run tests without building while we -# are at it. +# It appears that people try to run tests without building... ../git >/dev/null if test $? != 1 @@ -32,14 +28,6 @@ then exit 1 fi -merge >/dev/null 2>/dev/null -if test $? = 127 -then - echo >&2 'You do not seem to have "merge" installed. -Please check INSTALL document.' - exit 1 -fi - . ./test-lib.sh ################################################################ -- cgit v0.10.2-6-g49f6 From f953831e030d3ece7346bdb5c4fde4fde43c925e Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Dec 2006 04:05:39 +0100 Subject: merge-recursive: add/add really is modify/modify with an empty base Unify the handling for cases C (add/add) and D (modify/modify). Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/merge-recursive.c b/merge-recursive.c index 9d53bcd..58f2cb4 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -610,6 +610,12 @@ static void fill_mm(const unsigned char *sha1, mmfile_t *mm) unsigned long size; char type[20]; + if (!hashcmp(sha1, null_sha1)) { + mm->ptr = xstrdup(""); + mm->size = 0; + return; + } + mm->ptr = read_sha1_file(sha1, type, &size); if (!mm->ptr || strcmp(type, blob_type)) die("unable to read blob object %s", sha1_to_hex(sha1)); @@ -1045,38 +1051,17 @@ static int process_entry(const char *path, struct stage_data *entry, output("Adding %s", path); update_file(1, sha, mode, path); } - } else if (!o_sha && a_sha && b_sha) { - /* Case C: Added in both (check for same permissions). */ - if (sha_eq(a_sha, b_sha)) { - if (a_mode != b_mode) { - clean_merge = 0; - output("CONFLICT: File %s added identically in both branches, " - "but permissions conflict %06o->%06o", - path, a_mode, b_mode); - output("CONFLICT: adding with permission: %06o", a_mode); - update_file(0, a_sha, a_mode, path); - } else { - /* This case is handled by git-read-tree */ - assert(0 && "This case must be handled by git-read-tree"); - } - } else { - const char *new_path1, *new_path2; - clean_merge = 0; - new_path1 = unique_path(path, branch1); - new_path2 = unique_path(path, branch2); - output("CONFLICT (add/add): File %s added non-identically " - "in both branches. Adding as %s and %s instead.", - path, new_path1, new_path2); - remove_file(0, path, 0); - update_file(0, a_sha, a_mode, new_path1); - update_file(0, b_sha, b_mode, new_path2); - } - - } else if (o_sha && a_sha && b_sha) { + } else if (a_sha && b_sha) { + /* Case C: Added in both (check for same permissions) and */ /* case D: Modified in both, but differently. */ + const char *reason = "content"; struct merge_file_info mfi; struct diff_filespec o, a, b; + if (!o_sha) { + reason = "add/add"; + o_sha = (unsigned char *)null_sha1; + } output("Auto-merging %s", path); o.path = a.path = b.path = (char *)path; hashcpy(o.sha1, o_sha); @@ -1093,7 +1078,8 @@ static int process_entry(const char *path, struct stage_data *entry, update_file(1, mfi.sha, mfi.mode, path); else { clean_merge = 0; - output("CONFLICT (content): Merge conflict in %s", path); + output("CONFLICT (%s): Merge conflict in %s", + reason, path); if (index_only) update_file(0, mfi.sha, mfi.mode, path); diff --git a/t/t6024-recursive-merge.sh b/t/t6024-recursive-merge.sh old mode 100755 new mode 100644 index 9416c27..964010e --- a/t/t6024-recursive-merge.sh +++ b/t/t6024-recursive-merge.sh @@ -58,9 +58,19 @@ GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F test_expect_failure "combined merge conflicts" "git merge -m final G" +cat > expect << EOF +<<<<<<< HEAD/a1 +F +======= +G +>>>>>>> 26f86b677eb03d4d956dbe108b29cb77061c1e73/a1 +EOF + +test_expect_success "result contains a conflict" "diff -u expect a1" + git ls-files --stage > out cat > expect << EOF -100644 f70f10e4db19068f79bc43844b49f3eece45c4e8 1 a1 +100644 f16f906ab60483c100d1241dfc39868de9ec9fcb 1 a1 100644 cf84443e49e1b366fac938711ddf4be2d4d1d9e9 2 a1 100644 fd7923529855d0b274795ae3349c5e0438333979 3 a1 EOF -- cgit v0.10.2-6-g49f6 From 25fb62905890d7860f742b4f2215fdf754ae7fee Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 13 Dec 2006 00:59:58 -0800 Subject: git-push: document removal of remote ref with : pathspec Signed-off-by: Junio C Hamano diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index d4ae99f..197f4b5 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -49,12 +49,14 @@ corresponding remotes file---see below), then all the refs that exist both on the local side and on the remote side are updated. + -Some short-cut notations are also supported. +`tag ` means the same as `refs/tags/:refs/tags/`. + -* `tag ` means the same as `refs/tags/:refs/tags/`. -* A parameter without a colon is equivalent to - `:`, hence updates in the destination from - in the source. +A parameter without a colon is equivalent to +`:`, hence updates in the destination from +in the source. ++ +Pushing an empty allows you to delete the ref from +the remote repository. \--all:: Instead of naming each ref to push, specifies that all @@ -75,7 +77,8 @@ include::urls.txt[] Author ------ -Written by Junio C Hamano +Written by Junio C Hamano , later rewritten in C +by Linus Torvalds Documentation -------------- -- cgit v0.10.2-6-g49f6 From a159ca0cb7e0acdd37cb066327dcb020d95602d0 Mon Sep 17 00:00:00 2001 From: Andy Parkins Date: Wed, 13 Dec 2006 09:13:28 +0000 Subject: Allow subcommand.color and color.subcommand color configuration While adding colour to the branch command it was pointed out that a config option like "branch.color" conflicts with the pre-existing "branch.something" namespace used for specifying default merge urls and branches. The suggested solution was to flip the order of the components to "color.branch", which I did for colourising branch. This patch does the same thing for - git-log (color.diff) - git-status (color.status) - git-diff (color.diff) - pager (color.pager) I haven't removed the old config options; but they should probably be deprecated and eventually removed to prevent future namespace collisions. I've done this deprecation by changing the documentation for the config file to match the new names; and adding the "color.XXX" options to contrib/completion/git-completion.bash. Unfortunately git-svn reads "diff.color" and "pager.color"; which I don't like to change unilaterally. Signed-off-by: Andy Parkins Signed-off-by: Junio C Hamano diff --git a/Documentation/config.txt b/Documentation/config.txt index 21ec557..f5a552e 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -137,16 +137,16 @@ branch..merge:: this option, `git pull` defaults to merge the first refspec fetched. Specify multiple values to get an octopus merge. -pager.color:: +color.pager:: A boolean to enable/disable colored output when the pager is in use (default is true). -diff.color:: +color.diff:: When true (or `always`), always use colors in patch. When false (or `never`), never. When set to `auto`, use colors only when the output is to the terminal. -diff.color.:: +color.diff.:: Use customized color for diff colorization. `` specifies which part of the patch to use the specified color, and is one of `plain` (context text), `meta` @@ -271,19 +271,19 @@ showbranch.default:: The default set of branches for gitlink:git-show-branch[1]. See gitlink:git-show-branch[1]. -status.color:: +color.status:: A boolean to enable/disable color in the output of gitlink:git-status[1]. May be set to `true` (or `always`), `false` (or `never`) or `auto`, in which case colors are used only when the output is to a terminal. Defaults to false. -status.color.:: +color.status.:: Use customized color for status colorization. `` is one of `header` (the header text of the status message), `updated` (files which are updated but not committed), `changed` (files which are changed but not updated in the index), or `untracked` (files which are not tracked by git). The values of - these variables may be specified as in diff.color.. + these variables may be specified as in color.diff.. tar.umask:: By default, gitlink:git-tar-tree[1] sets file and directories modes diff --git a/builtin-log.c b/builtin-log.c index 7acf5d3..6821a08 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -118,7 +118,7 @@ static int git_format_config(const char *var, const char *value) strcat(extra_headers, value); return 0; } - if (!strcmp(var, "diff.color")) { + if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) { return 0; } return git_log_config(var, value); diff --git a/config.c b/config.c index 3cae390..1bdef44 100644 --- a/config.c +++ b/config.c @@ -314,7 +314,7 @@ int git_default_config(const char *var, const char *value) return 0; } - if (!strcmp(var, "pager.color")) { + if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) { pager_use_color = git_config_bool(var,value); return 0; } diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 447ec20..9c4d23a 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -712,10 +712,13 @@ _git_repo_config () core.legacyHeaders i18n.commitEncoding diff.color + color.diff diff.renameLimit diff.renames pager.color + color.pager status.color + color.status log.showroot show.difftree showbranch.default diff --git a/diff.c b/diff.c index 3315378..726b01e 100644 --- a/diff.c +++ b/diff.c @@ -60,7 +60,7 @@ int git_diff_ui_config(const char *var, const char *value) diff_rename_limit_default = git_config_int(var, value); return 0; } - if (!strcmp(var, "diff.color")) { + if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) { diff_use_color_default = git_config_colorbool(var, value); return 0; } @@ -74,7 +74,7 @@ int git_diff_ui_config(const char *var, const char *value) diff_detect_rename_default = DIFF_DETECT_RENAME; return 0; } - if (!strncmp(var, "diff.color.", 11)) { + if (!strncmp(var, "diff.color.", 11) || !strncmp(var, "color.diff.", 11)) { int slot = parse_diff_color_slot(var, 11); color_parse(value, var, diff_colors[slot]); return 0; diff --git a/wt-status.c b/wt-status.c index de1be5b..df582a0 100644 --- a/wt-status.c +++ b/wt-status.c @@ -297,11 +297,11 @@ void wt_status_print(struct wt_status *s) int git_status_config(const char *k, const char *v) { - if (!strcmp(k, "status.color")) { + if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) { wt_status_use_color = git_config_colorbool(k, v); return 0; } - if (!strncmp(k, "status.color.", 13)) { + if (!strncmp(k, "status.color.", 13) || !strncmp(k, "color.status", 13)) { int slot = parse_status_slot(k, 13); color_parse(v, k, wt_status_colors[slot]); } -- cgit v0.10.2-6-g49f6 From b42a044f5942c64bbc94aa2f4637d8290748e634 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 24 Nov 2006 22:38:18 -0800 Subject: git-svn: exit with status 1 for test failures Some versions of the SVN libraries cause die() to exit with 255, and 40cf043389ef4cdf3e56e7c4268d6f302e387fa0 tightened up test_expect_failure to reject return values >128. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index b53273e..e8b5c09 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -21,6 +21,7 @@ $ENV{TZ} = 'UTC'; $ENV{LC_ALL} = 'C'; $| = 1; # unbuffer STDOUT +sub fatal (@) { print STDERR $@; exit 1 } # If SVN:: library support is added, please make the dependencies # optional and preserve the capability to use the command-line client. # use eval { require SVN::... } to make it lazy load @@ -571,7 +572,7 @@ sub commit_lib { $no = 1; } } - close $fh or croak $?; + close $fh or exit 1; if (! defined $r_new && ! defined $cmt_new) { unless ($no) { die "Failed to parse revision information\n"; @@ -873,13 +874,16 @@ sub commit_diff { print "Committed $_[0]\n"; }, @lock) ); - my $mods = libsvn_checkout_tree($ta, $tb, $ed); - if (@$mods == 0) { - print "No changes\n$ta == $tb\n"; - $ed->abort_edit; - } else { - $ed->close_edit; - } + eval { + my $mods = libsvn_checkout_tree($ta, $tb, $ed); + if (@$mods == 0) { + print "No changes\n$ta == $tb\n"; + $ed->abort_edit; + } else { + $ed->close_edit; + } + }; + fatal "$@\n" if $@; $_message = $_file = undef; return $rev_committed; } -- cgit v0.10.2-6-g49f6 From 155bd0ce23144e5c7067965a22646523f1a38b51 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 12 Dec 2006 14:47:02 -0800 Subject: git-svn: correctly display fatal() error messages If I wanted to print $@, I'd pass $@ to fatal(). This looks like a stupid typo on my part. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index e8b5c09..599edc3 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -21,7 +21,7 @@ $ENV{TZ} = 'UTC'; $ENV{LC_ALL} = 'C'; $| = 1; # unbuffer STDOUT -sub fatal (@) { print STDERR $@; exit 1 } +sub fatal (@) { print STDERR @_; exit 1 } # If SVN:: library support is added, please make the dependencies # optional and preserve the capability to use the command-line client. # use eval { require SVN::... } to make it lazy load -- cgit v0.10.2-6-g49f6 From f2dd1c9adfe961ea611545b018bc67e5d83ee3db Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 13 Dec 2006 01:33:43 -0800 Subject: Revert "git-diff: Introduce --index and deprecate --cached." This reverts commit 4c81c213a479e4aae0653a56ad6e8db5c31f019c. Although --cached and --index are confusing wording, the use of word --cached for git-diff is consistent with git-apply. It means "work with index without looking at the working tree". We should probably come up with better wording for --cached, if somebody wants to deprecate it. But making --index and --cached synonyms for diff while leaving them mean different things for apply is no good. diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt index 3144864..228c4d9 100644 --- a/Documentation/git-diff.txt +++ b/Documentation/git-diff.txt @@ -22,10 +22,8 @@ the number of trees given to the command. * When one is given, the working tree and the named tree are compared, using `git-diff-index`. The option - `--index` can be given to compare the index file and + `--cached` can be given to compare the index file and the named tree. - `--cached` is a deprecated alias for `--index`. It's use is - discouraged. * When two s are given, these two trees are compared using `git-diff-tree`. @@ -49,7 +47,7 @@ Various ways to check your working tree:: + ------------ $ git diff <1> -$ git diff --index <2> +$ git diff --cached <2> $ git diff HEAD <3> ------------ + diff --git a/builtin-diff.c b/builtin-diff.c index 1c535b1..a659020 100644 --- a/builtin-diff.c +++ b/builtin-diff.c @@ -137,7 +137,7 @@ static int builtin_diff_index(struct rev_info *revs, int cached = 0; while (1 < argc) { const char *arg = argv[1]; - if (!strcmp(arg, "--index") || !strcmp(arg, "--cached")) + if (!strcmp(arg, "--cached")) cached = 1; else usage(builtin_diff_usage); -- cgit v0.10.2-6-g49f6 From 8371234ecaaf6e14fe3f2082a855eff1bbd79ae9 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Wed, 13 Dec 2006 05:42:44 -0500 Subject: Remove uncontested renamed files during merge. Prior to 65ac6e9c3f47807cb603af07a6a9e1a43bc119ae we deleted a file from the working directory during a merge if the file existed before the merge started but was renamed by the branch being merged in. This broke in 65ac6e as git-merge-recursive did not actually update the working directory on an uncontested rename. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/merge-recursive.c b/merge-recursive.c index 32e186c..866a4e4 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -889,7 +889,7 @@ static int process_renames(struct path_list *a_renames, struct diff_filespec src_other, dst_other; int try_merge, stage = a_renames == renames1 ? 3: 2; - remove_file(1, ren1_src, 1); + remove_file(1, ren1_src, index_only); hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha); src_other.mode = ren1->src_entry->stages[stage].mode; diff --git a/t/t6023-merge-rename-nocruft.sh b/t/t6023-merge-rename-nocruft.sh new file mode 100755 index 0000000..69c66cf --- /dev/null +++ b/t/t6023-merge-rename-nocruft.sh @@ -0,0 +1,97 @@ +#!/bin/sh + +test_description='Merge-recursive merging renames' +. ./test-lib.sh + +test_expect_success setup \ +' +cat >A <<\EOF && +a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +b bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +c cccccccccccccccccccccccccccccccccccccccccccccccc +d dddddddddddddddddddddddddddddddddddddddddddddddd +e eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +f ffffffffffffffffffffffffffffffffffffffffffffffff +g gggggggggggggggggggggggggggggggggggggggggggggggg +h hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh +i iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii +j jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj +k kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk +l llllllllllllllllllllllllllllllllllllllllllllllll +m mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm +n nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn +o oooooooooooooooooooooooooooooooooooooooooooooooo +EOF + +cat >M <<\EOF && +A AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +B BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB +C CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC +D DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD +E EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +F FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +G GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG +H HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +I IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII +J JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ +K KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK +L LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL +M MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM +N NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +O OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO +EOF + +git add A M && +git commit -m "initial has A and M" && +git branch white && +git branch red && + +git checkout white && +sed -e "/^g /s/.*/g : white changes a line/" B && +sed -e "/^G /s/.*/G : colored branch changes a line/" N && +rm -f A M && +git update-index --add --remove A B M N && +git commit -m "white renames A->B, M->N" && + +git checkout red && +echo created by red >R && +git update-index --add R && +git commit -m "red creates R" && + +git checkout master' + +# This test broke in 65ac6e9c3f47807cb603af07a6a9e1a43bc119ae +test_expect_success 'merge white into red (A->B,M->N)' \ +' + git checkout -b red-white red && + git merge white && + git write-tree >/dev/null || { + echo "BAD: merge did not complete" + return 1 + } + + test -f B || { + echo "BAD: B does not exist in working directory" + return 1 + } + test -f N || { + echo "BAD: N does not exist in working directory" + return 1 + } + test -f R || { + echo "BAD: R does not exist in working directory" + return 1 + } + + test -f A && { + echo "BAD: A still exists in working directory" + return 1 + } + test -f M && { + echo "BAD: M still exists in working directory" + return 1 + } + return 0 +' + +test_done -- cgit v0.10.2-6-g49f6 From 0c4e95d083f77de03a64b65f5633ed0ba082a26e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 13 Dec 2006 09:32:40 -0800 Subject: git merge: reword failure message. 99.9999% of the time, the command is used with a single strategy; after a merge failure, saying "No strategy handled the merge" is technically correct, but there is no point stressing we tried and failed all the possibilities the user has given. Just say that it failed. Signed-off-by: Junio C Hamano diff --git a/git-merge.sh b/git-merge.sh index a948878..2f3d936 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -400,7 +400,14 @@ fi case "$best_strategy" in '') restorestate - echo >&2 "No merge strategy handled the merge." + case "$use_strategies" in + ?*' '?*) + echo >&2 "No merge strategy handled the merge." + ;; + *) + echo >&2 "Merge with strategy $use_strategies failed." + ;; + esac exit 2 ;; "$wt_strategy") -- cgit v0.10.2-6-g49f6 From 7ef0435088f41165ece95b6f226d3c15438505a5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 13 Dec 2006 00:58:28 -0800 Subject: spurious .sp in manpages This is just a random hack to work around problems people seem to be seeing in manpage backend of xmlto (it appears we are getting ".sp" at the end of line without line break). Could people test this out? Signed-off-by: Junio C Hamano diff --git a/Documentation/callouts.xsl b/Documentation/callouts.xsl index ad03755..6a361a2 100644 --- a/Documentation/callouts.xsl +++ b/Documentation/callouts.xsl @@ -13,4 +13,18 @@ .br + + + + + + + + + + + + -- cgit v0.10.2-6-g49f6 From 411fb8baa6862b76f7bdd9fc0d5844855a4db589 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 13 Dec 2006 10:03:39 -0800 Subject: git-push: accept tag as advertised. The documentation talked about "git push $URL tag " as a short-hand for refs/tags/:refs/tags/ for a long time but that was never the case (the short-hand was for "git fetch"). Instead of fixing the documentation, just add a bit of code to match it since it is easy to do and would make it more consistent. Signed-off-by: Junio C Hamano diff --git a/builtin-push.c b/builtin-push.c index d23974e..b7412e8 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -57,11 +57,36 @@ static void expand_refspecs(void) static void set_refspecs(const char **refs, int nr) { if (nr) { - size_t bytes = nr * sizeof(char *); - - refspec = xrealloc(refspec, bytes); - memcpy(refspec, refs, bytes); - refspec_nr = nr; + int pass; + for (pass = 0; pass < 2; pass++) { + /* pass 0 counts and allocates, pass 1 fills */ + int i, cnt; + for (i = cnt = 0; i < nr; i++) { + if (!strcmp("tag", refs[i])) { + int len; + char *tag; + if (nr <= ++i) + die("tag shorthand without "); + if (pass) { + len = strlen(refs[i]) + 11; + tag = xmalloc(len); + strcpy(tag, "refs/tags/"); + strcat(tag, refs[i]); + refspec[cnt] = tag; + } + cnt++; + continue; + } + if (pass) + refspec[cnt] = refs[i]; + cnt++; + } + if (!pass) { + size_t bytes = cnt * sizeof(char *); + refspec_nr = cnt; + refspec = xrealloc(refspec, bytes); + } + } } expand_refspecs(); } -- cgit v0.10.2-6-g49f6 From 37adac765a469f8f8495e2befe7afeda65a2b272 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 13 Dec 2006 10:30:11 -0800 Subject: send-pack: tighten checks for remote names "git push $URL HEAD~6" created a bogus ref HEAD~6 immediately under $GIT_DIR of the remote repository. While we should keep refspecs that have arbitrary extended SHA-1 expression on the source side working (e.g. "HEAD~6:refs/tags/yesterday"), we should not create bogus ref on the other end. Signed-off-by: Junio C Hamano diff --git a/refs.c b/refs.c index 96ea8b6..e56abb8 100644 --- a/refs.c +++ b/refs.c @@ -534,7 +534,7 @@ int check_ref_format(const char *ref) level++; if (!ch) { if (level < 2) - return -1; /* at least of form "heads/blah" */ + return -2; /* at least of form "heads/blah" */ return 0; } } diff --git a/send-pack.c b/send-pack.c index 328dbbc..cc884f3 100644 --- a/send-pack.c +++ b/send-pack.c @@ -406,6 +406,25 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) return ret; } +static void verify_remote_names(int nr_heads, char **heads) +{ + int i; + + for (i = 0; i < nr_heads; i++) { + const char *remote = strchr(heads[i], ':'); + + remote = remote ? (remote + 1) : heads[i]; + switch (check_ref_format(remote)) { + case 0: /* ok */ + case -2: /* ok but a single level -- that is fine for + * a match pattern. + */ + continue; + } + die("remote part of refspec is not a valid name in %s", + heads[i]); + } +} int main(int argc, char **argv) { @@ -457,6 +476,8 @@ int main(int argc, char **argv) usage(send_pack_usage); if (heads && send_all) usage(send_pack_usage); + verify_remote_names(nr_heads, heads); + pid = git_connect(fd, dest, exec); if (pid < 0) return 1; -- cgit v0.10.2-6-g49f6 From 753f96a455534ad60b670376fb3d89179281e541 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 13 Dec 2006 10:55:21 -0800 Subject: branch --color: change default color selection. Showing local and remote branches in green and red was simply overkill, as all we wanted was to make it easy to tell them apart (local ones can be built on top by committing, but the remote tracking ones can't). Use plain coloring for local branches and paint remotes in red. Signed-off-by: Junio C Hamano diff --git a/builtin-branch.c b/builtin-branch.c index 7c87b8d..d1c243d 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -23,8 +23,8 @@ static char branch_colors[][COLOR_MAXLEN] = { "\033[m", /* reset */ "", /* PLAIN (normal) */ "\033[31m", /* REMOTE (red) */ - "\033[32m", /* LOCAL (green) */ - "\033[1;32m", /* CURRENT (boldgreen) */ + "", /* LOCAL (normal) */ + "\033[32m", /* CURRENT (green) */ }; enum color_branch { COLOR_BRANCH_RESET = 0, -- cgit v0.10.2-6-g49f6 From b11121d9e330c40f5d089636f176d089e5bb1885 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 1 Dec 2006 20:45:45 -0800 Subject: git-blame: show lines attributed to boundary commits differently. When blaming with revision ranges, often many lines are attributed to different commits at the boundary, but they are not interesting for the purpose of finding project history during that revision range. This outputs the lines blamed on boundary commits differently. When showing "human format" output, their SHA-1 are shown with '^' prefixed. In "porcelain format", the commit will be shown with an extra attribute line "boundary". Signed-off-by: Junio C Hamano diff --git a/builtin-blame.c b/builtin-blame.c index dc3ffea..a250724 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -1090,6 +1090,11 @@ static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt) if (!(commit->object.flags & UNINTERESTING) && !(revs->max_age != -1 && commit->date < revs->max_age)) pass_blame(sb, suspect, opt); + else { + commit->object.flags |= UNINTERESTING; + if (commit->object.parsed) + mark_parents_uninteresting(commit); + } /* Take responsibility for the remaining entries */ for (ent = sb->ent; ent; ent = ent->next) @@ -1273,6 +1278,8 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent) printf("committer-tz %s\n", ci.committer_tz); printf("filename %s\n", suspect->path); printf("summary %s\n", ci.summary); + if (suspect->commit->object.flags & UNINTERESTING) + printf("boundary\n"); } else if (suspect->commit->object.flags & MORE_THAN_ONE_PATH) printf("filename %s\n", suspect->path); @@ -1308,8 +1315,14 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) cp = nth_line(sb, ent->lno); for (cnt = 0; cnt < ent->num_lines; cnt++) { char ch; + int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8; + + if (suspect->commit->object.flags & UNINTERESTING) { + length--; + putchar('^'); + } - printf("%.*s", (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8, hex); + printf("%.*s", length, hex); if (opt & OUTPUT_ANNOTATE_COMPAT) printf("\t(%10s\t%10s\t%d)", ci.author, format_time(ci.author_time, ci.author_tz, -- cgit v0.10.2-6-g49f6 From 359850041e8158f6aeb70ad611ef1ba8834b8349 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 11 Dec 2006 20:25:58 -0800 Subject: git-svn: correctly handle "(no author)" when using an authors file The low-level parts of the SVN library return NULL/undef for author-less revisions, whereas "(no author)" is a (svn) client convention. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index 15254e4..ec92b44 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2981,7 +2981,8 @@ sub libsvn_log_entry { my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T (\d\d)\:(\d\d)\:(\d\d).\d+Z$/x) or die "Unable to parse date: $date\n"; - if (defined $_authors && ! defined $users{$author}) { + if (defined $author && length $author > 0 && + defined $_authors && ! defined $users{$author}) { die "Author: $author not defined in $_authors file\n"; } $msg = '' if ($rev == 0 && !defined $msg); -- cgit v0.10.2-6-g49f6 From 1d77043b005921cf7fcebfe680777df23ad10119 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 13 Dec 2006 12:11:03 -0800 Subject: config documentation: group color items together. Signed-off-by: Junio C Hamano diff --git a/Documentation/config.txt b/Documentation/config.txt index f5a552e..a3587f8 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -137,10 +137,6 @@ branch..merge:: this option, `git pull` defaults to merge the first refspec fetched. Specify multiple values to get an octopus merge. -color.pager:: - A boolean to enable/disable colored output when the pager is in - use (default is true). - color.diff:: When true (or `always`), always use colors in patch. When false (or `never`), never. When set to `auto`, use @@ -157,6 +153,24 @@ color.diff.:: `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, or `white`. +color.pager:: + A boolean to enable/disable colored output when the pager is in + use (default is true). + +color.status:: + A boolean to enable/disable color in the output of + gitlink:git-status[1]. May be set to `true` (or `always`), + `false` (or `never`) or `auto`, in which case colors are used + only when the output is to a terminal. Defaults to false. + +color.status.:: + Use customized color for status colorization. `` is + one of `header` (the header text of the status message), + `updated` (files which are updated but not committed), + `changed` (files which are changed but not updated in the index), + or `untracked` (files which are not tracked by git). The values of + these variables may be specified as in color.diff.. + diff.renameLimit:: The number of files to consider when performing the copy/rename detection; equivalent to the git diff option '-l'. @@ -271,20 +285,6 @@ showbranch.default:: The default set of branches for gitlink:git-show-branch[1]. See gitlink:git-show-branch[1]. -color.status:: - A boolean to enable/disable color in the output of - gitlink:git-status[1]. May be set to `true` (or `always`), - `false` (or `never`) or `auto`, in which case colors are used - only when the output is to a terminal. Defaults to false. - -color.status.:: - Use customized color for status colorization. `` is - one of `header` (the header text of the status message), - `updated` (files which are updated but not committed), - `changed` (files which are changed but not updated in the index), - or `untracked` (files which are not tracked by git). The values of - these variables may be specified as in color.diff.. - tar.umask:: By default, gitlink:git-tar-tree[1] sets file and directories modes to 0666 or 0777. While this is both useful and acceptable for projects -- cgit v0.10.2-6-g49f6 From ad2c82c0e1a543f4475a948ccb3eb4afcce86f26 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 13 Dec 2006 16:25:26 -0500 Subject: repacked packs should be read-only ... just like the other pack creating tools do. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano diff --git a/git-repack.sh b/git-repack.sh index f150a55..067898f 100755 --- a/git-repack.sh +++ b/git-repack.sh @@ -67,6 +67,8 @@ name=$(git-pack-objects --non-empty --all $args Date: Wed, 13 Dec 2006 15:58:41 -0800 Subject: git-svn: allow both diff.color and color.diff The list concensus is to group color related configuration under "color.*" so let's be consistent. Inspired by Andy Parkins's patch to do the same for diff/log family. With fixes from Eric Wong. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano diff --git a/git-svn.perl b/git-svn.perl index ec92b44..73ab8d8 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -925,19 +925,38 @@ sub cmt_showable { sub log_use_color { return 1 if $_color; - my $dc; - chomp($dc = `git-repo-config --get diff.color`); + my ($dc, $dcvar); + $dcvar = 'color.diff'; + $dc = `git-repo-config --get $dcvar`; + if ($dc eq '') { + # nothing at all; fallback to "diff.color" + $dcvar = 'diff.color'; + $dc = `git-repo-config --get $dcvar`; + } + chomp($dc); if ($dc eq 'auto') { - if (-t *STDOUT || (defined $_pager && - `git-repo-config --bool --get pager.color` !~ /^false/)) { + my $pc; + $pc = `git-repo-config --get color.pager`; + if ($pc eq '') { + # does not have it -- fallback to pager.color + $pc = `git-repo-config --bool --get pager.color`; + } + else { + $pc = `git-repo-config --bool --get color.pager`; + if ($?) { + $pc = 'false'; + } + } + chomp($pc); + if (-t *STDOUT || (defined $_pager && $pc eq 'true')) { return ($ENV{TERM} && $ENV{TERM} ne 'dumb'); } return 0; } return 0 if $dc eq 'never'; return 1 if $dc eq 'always'; - chomp($dc = `git-repo-config --bool --get diff.color`); - $dc eq 'true'; + chomp($dc = `git-repo-config --bool --get $dcvar`); + return ($dc eq 'true'); } sub git_svn_log_cmd { -- cgit v0.10.2-6-g49f6 From f5e6b89b3a4420481a6ecdc05df325cb6d69d114 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 14 Dec 2006 00:03:18 -0800 Subject: Update git-diff documentation Porcelain documentation should talk in terms of end-user workflow, not in terms of implementation details. Do not suggest update-index, but git-add instead. Explain differences among 0-, 1- and 2-tree cases not as differences of number of trees given to the command, but say why user would want to give these number of trees to the command in what situation. Signed-off-by: Junio C Hamano diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt index 228c4d9..127d68c 100644 --- a/Documentation/git-diff.txt +++ b/Documentation/git-diff.txt @@ -14,30 +14,48 @@ DESCRIPTION ----------- Show changes between two trees, a tree and the working tree, a tree and the index file, or the index file and the working tree. -The combination of what is compared with what is determined by -the number of trees given to the command. -* When no is given, the working tree and the index - file are compared, using `git-diff-files`. +'git-diff' [--options] [--] [...]:: -* When one is given, the working tree and the named - tree are compared, using `git-diff-index`. The option - `--cached` can be given to compare the index file and - the named tree. + This form is to view the changes you made relative to + the index (staging area for the next commit). In other + words, the differences are what you _could_ tell git to + further add to the index but you still haven't. You can + stage these changes by using gitlink:git-add[1]. + +'git-diff' [--options] --cached [] [--] [...]:: + + This form is to view the changes you staged for the next + commit relative to the named . Typically you + would want comparison with the latest commit, so if you + do not give , it defaults to HEAD. + +'git-diff' [--options] -- [...]:: + + This form is to view the changes you have in your + working tree relative to the named . You can + use HEAD to compare it with the latest commit, or a + branch name to compare with the tip of a different + branch. + +'git-diff' [--options] -- [...]:: + + This form is to view the changes between two , + for example, tips of two branches. + +Just in case if you are doing something exotic, it should be +noted that all of the in the above description can be +any . -* When two s are given, these two trees are compared - using `git-diff-tree`. OPTIONS ------- ---diff-options:: - '--diff-options' are passed to the `git-diff-files`, - `git-diff-index`, and `git-diff-tree` commands. See the - documentation for these commands for description. +include::diff-options.txt[] ...:: - The arguments are also passed to `git-diff-\*` - commands. + The parameters, when given, are used to limit + the diff to the named paths (you can give directory + names and get diff for all files under them). EXAMPLES @@ -51,7 +69,7 @@ $ git diff --cached <2> $ git diff HEAD <3> ------------ + -<1> changes in the working tree since your last git-update-index. +<1> changes in the working tree not yet staged for the next commit. <2> changes between the index and your last commit; what you would be committing if you run "git commit" without "-a" option. <3> changes in the working tree since your last commit; what you -- cgit v0.10.2-6-g49f6 From 7da41f48c8acea834e8204917fe59da2b975903b Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 14 Dec 2006 05:07:46 -0500 Subject: Bypass expensive content comparsion during rename detection. When comparing file contents during the second loop through a rename detection attempt we can skip the expensive byte-by-byte comparsion if both source and destination files have valid SHA1 values. This improves performance by avoiding either an expensive open/mmap to read the working tree copy, or an expensive inflate of a blob object. Unfortunately we still have to at least initialize the sizes of the source and destination files even if the SHA1 values don't match. Failing to initialize the sizes causes a number of test cases to fail and start reporting different copy/rename behavior than was expected. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/diffcore-rename.c b/diffcore-rename.c index 57a74b6..91fa2be 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -109,6 +109,8 @@ static int is_exact_match(struct diff_filespec *src, return 0; if (src->size != dst->size) return 0; + if (src->sha1_valid && dst->sha1_valid) + return !hashcmp(src->sha1, dst->sha1); if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0)) return 0; if (src->size == dst->size && -- cgit v0.10.2-6-g49f6 From 02c9e93547d4c21635beb30895ebb6e37f67833c Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 14 Dec 2006 11:40:21 +0100 Subject: INSTALL: no need to have GNU diff installed Since a long time, we have inbuilt diff generation. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/INSTALL b/INSTALL index b5dd9f0..e7aea60 100644 --- a/INSTALL +++ b/INSTALL @@ -72,16 +72,6 @@ Issues of note: - expat library; git-http-push uses it for remote lock management over DAV. Similar to "curl" above, this is optional. - - "GNU diff" to generate patches. Of course, you don't _have_ to - generate patches if you don't want to, but let's face it, you'll - be wanting to. Or why did you get git in the first place? - - Non-GNU versions of the diff/patch programs don't generally support - the unified patch format (which is the one git uses), so you - really do want to get the GNU one. Trust me, you will want to - do that even if it wasn't for git. There's no point in living - in the dark ages any more. - - "wish", the Tcl/Tk windowing shell is used in gitk to show the history graphically -- cgit v0.10.2-6-g49f6 From 4da9028578ffaaf8985e1436e2e1cf16bd3b9023 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 14 Dec 2006 00:36:23 -0800 Subject: git-fetch: make it work from within a subdirectory. Signed-off-by: Junio C Hamano diff --git a/git-fetch.sh b/git-fetch.sh index 4eecf14..fb35815 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -2,7 +2,13 @@ # USAGE=' ...' +SUBDIRECTORY_OK=Yes . git-sh-setup +TOP=$(git-rev-parse --show-cdup) +if test ! -z "$TOP" +then + cd "$TOP" +fi . git-parse-remote _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40" -- cgit v0.10.2-6-g49f6 From a81c311f23a5fadd6c1da38d46781644cd9db6e8 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 14 Dec 2006 00:40:15 -0800 Subject: git-reset: make it work from within a subdirectory. If you typically sit in, say "src/", it's annoying to have to change directory to do a reset. This may need to be reworked when we add "git reset -- paths..." to encapsulate the "ls-tree $tree | update-index --index-info" pattern. Signed-off-by: Junio C Hamano diff --git a/git-reset.sh b/git-reset.sh index c0feb44..03d2c3b 100755 --- a/git-reset.sh +++ b/git-reset.sh @@ -1,8 +1,15 @@ #!/bin/sh USAGE='[--mixed | --soft | --hard] []' +SUBDIRECTORY_OK=Yes . git-sh-setup +TOP=$(git-rev-parse --show-cdup) +if test ! -z "$TOP" +then + cd "$TOP" +fi + update= reset_type=--mixed case "$1" in -- cgit v0.10.2-6-g49f6 From 2ce633b928f78224a37308f45810e76fefe8df56 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 14 Dec 2006 01:19:19 -0800 Subject: git-reset [--mixed] [--] ... Sometimes it is asked on the list how to revert selected path in the index from a tree, most often HEAD, without affecting the files in the working tree. A similar operation that also affects the working tree files has been available in the form of "git checkout -- ...". By definition --soft would never affect either the index nor the working tree files, and --hard is the way to make the working tree files as close to pristine, so this new option is available only for the default --mixed case. Signed-off-by: Junio C Hamano diff --git a/git-reset.sh b/git-reset.sh index 03d2c3b..8d95e37 100755 --- a/git-reset.sh +++ b/git-reset.sh @@ -1,35 +1,60 @@ #!/bin/sh - -USAGE='[--mixed | --soft | --hard] []' +# +# Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano +# +USAGE='[--mixed | --soft | --hard] [] [ [--] ...]' SUBDIRECTORY_OK=Yes . git-sh-setup +update= reset_type=--mixed +unset rev + +while case $# in 0) break ;; esac +do + case "$1" in + --mixed | --soft | --hard) + reset_type="$1" + ;; + --) + break + ;; + -*) + usage + ;; + *) + rev=$(git-rev-parse --verify "$1") || exit + shift + break + ;; + esac + shift +done + +: ${rev=HEAD} +rev=$(git-rev-parse --verify $rev^0) || exit + +# Skip -- in "git reset HEAD -- foo" and "git reset -- foo". +case "$1" in --) shift ;; esac + +# git reset --mixed tree [--] paths... can be used to +# load chosen paths from the tree into the index without +# affecting the working tree nor HEAD. +if test $# != 0 +then + test "$reset_type" == "--mixed" || + die "Cannot do partial $reset_type reset." + git ls-tree -r --full-name $rev -- "$@" | + git update-index --add --index-info || exit + git update-index --refresh + exit +fi + TOP=$(git-rev-parse --show-cdup) if test ! -z "$TOP" then cd "$TOP" fi -update= -reset_type=--mixed -case "$1" in ---mixed | --soft | --hard) - reset_type="$1" - shift - ;; --*) - usage ;; -esac - -case $# in -0) rev=HEAD ;; -1) rev=$(git-rev-parse --verify "$1") || exit ;; -*) usage ;; -esac -rev=$(git-rev-parse --verify $rev^0) || exit - -# We need to remember the set of paths that _could_ be left -# behind before a hard reset, so that we can remove them. if test "$reset_type" = "--hard" then update=-u -- cgit v0.10.2-6-g49f6 From 5d7eeee2ac64e277e47ce2cdabd1af0d2501a96f Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 14 Dec 2006 11:31:05 +0100 Subject: git-show: grok blobs, trees and tags, too Since git-show is pure Porcelain, it is the ideal candidate to pretty print other things than commits, too. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/Documentation/git-show.txt b/Documentation/git-show.txt index 4c880a8..98dea61 100644 --- a/Documentation/git-show.txt +++ b/Documentation/git-show.txt @@ -3,20 +3,27 @@ git-show(1) NAME ---- -git-show - Show one commit with difference it introduces +git-show - Show various types of objects SYNOPSIS -------- -'git-show'