1
0
mirror of https://github.com/git/git.git synced 2024-11-18 23:13:58 +01:00

Merge branch 'jc/test-clone' into jc/clone

* jc/test-clone: (35 commits)
  Introduce GIT_TEMPLATE_DIR
  Revert "fix testsuite: make sure they use templates freshly built from the source"
  fix testsuite: make sure they use templates freshly built from the source
  rerere: fix breakage of resolving.
  Add config example with respect to branch
  Add documentation for show-branch --topics
  make git a bit less cryptic on fetch errors
  make patch_delta() error cases a bit more verbose
  racy-git: documentation updates.
  show-ref: fix --exclude-existing
  parse-remote::expand_refs_wildcard()
  vim syntax: follow recent changes to commit template
  show-ref: fix --verify --hash=length
  show-ref: fix --quiet --verify
  avoid accessing _all_ loose refs in git-show-ref --verify
  git-fetch: Avoid reading packed refs over and over again
  Teach show-branch how to show ref-log data.
  markup fix in svnimport documentation.
  Documentation: new option -P for git-svnimport
  Fix mis-mark-up in git-merge-file.txt documentation
  ...
This commit is contained in:
Junio C Hamano 2006-12-19 01:38:18 -08:00
commit 5fed466815
29 changed files with 564 additions and 106 deletions

@ -31,6 +31,11 @@ Example
external = "/usr/local/bin/gnu-diff -u"
renames = true
[branch "devel"]
remote = origin
merge = refs/heads/devel
Variables
~~~~~~~~~

@ -1,9 +1,9 @@
git-merge-file(1)
============
=================
NAME
----
git-merge-file - threeway file merge
git-merge-file - three-way file merge
SYNOPSIS

@ -10,7 +10,7 @@ SYNOPSIS
[verse]
'git-show-branch' [--all] [--heads] [--tags] [--topo-order] [--current]
[--more=<n> | --list | --independent | --merge-base]
[--no-name | --sha1-name] [<rev> | <glob>]...
[--no-name | --sha1-name] [--topics] [<rev> | <glob>]...
DESCRIPTION
-----------
@ -86,6 +86,14 @@ OPTIONS
of "master"), name them with the unique prefix of their
object names.
--topics::
Shows only commits that are NOT on the first branch given.
This helps track topic branches by hiding any commit that
is already in the main line of development. When given
"git show-branch --topics master topic1 topic2", this
will show the revisions given by "git rev-list {caret}master
topic1 topic2"
Note that --more, --list, --independent and --merge-base options
are mutually exclusive.

@ -15,6 +15,7 @@ SYNOPSIS
[ -b branch_subdir ] [ -T trunk_subdir ] [ -t tag_subdir ]
[ -s start_chg ] [ -m ] [ -r ] [ -M regex ]
[ -I <ignorefile_name> ] [ -A <author_file> ]
[ -P <path_from_trunk> ]
<SVN_repository_URL> [ <path> ]
@ -103,9 +104,17 @@ repository without -A.
-l <max_rev>::
Specify a maximum revision number to pull.
+
Formerly, this option controlled how many revisions to pull,
due to SVN memory leaks. (These have been worked around.)
Formerly, this option controlled how many revisions to pull,
due to SVN memory leaks. (These have been worked around.)
-P <path_from_trunk>::
Partial import of the SVN tree.
+
By default, the whole tree on the SVN trunk (/trunk) is imported.
'-P my/proj' will import starting only from '/trunk/my/proj'.
This option is useful when you want to import one project from a
svn repo which hosts multiple projects under the same trunk.
-v::
Verbosity: let 'svnimport' report what it is doing.

@ -4,7 +4,7 @@ Use of index and Racy git problem
Background
----------
The index is one of the most important data structure in git.
The index is one of the most important data structures in git.
It represents a virtual working tree state by recording list of
paths and their object names and serves as a staging area to
write out the next tree object to be committed. The state is
@ -16,7 +16,7 @@ virtual working tree state in the index and the files in the
working tree. The most obvious case is when the user asks `git
diff` (or its low level implementation, `git diff-files`) or
`git-ls-files --modified`. In addition, git internally checks
if the files in the working tree is different from what are
if the files in the working tree are different from what are
recorded in the index to avoid stomping on local changes in them
during patch application, switching branches, and merging.
@ -24,9 +24,9 @@ In order to speed up this comparison between the files in the
working tree and the index entries, the index entries record the
information obtained from the filesystem via `lstat(2)` system
call when they were last updated. When checking if they differ,
git first runs `lstat(2)` on the files and compare the result
git first runs `lstat(2)` on the files and compares the result
with this information (this is what was originally done by the
`ce_match_stat()` function, which the current code does in
`ce_match_stat()` function, but the current code does it in
`ce_match_stat_basic()` function). If some of these "cached
stat information" fields do not match, git can tell that the
files are modified without even looking at their contents.
@ -53,8 +53,9 @@ Racy git
There is one slight problem with the optimization based on the
cached stat information. Consider this sequence:
: modify 'foo'
$ git update-index 'foo'
: modify 'foo' in-place without changing its size
: modify 'foo' again, in-place, without changing its size
The first `update-index` computes the object name of the
contents of file `foo` and updates the index entry for `foo`
@ -62,7 +63,8 @@ along with the `struct stat` information. If the modification
that follows it happens very fast so that the file's `st_mtime`
timestamp does not change, after this sequence, the cached stat
information the index entry records still exactly match what you
can obtain from the filesystem, but the file `foo` is modified.
would see in the filesystem, even though the file `foo` is now
different.
This way, git can incorrectly think files in the working tree
are unmodified even though they actually are. This is called
the "racy git" problem (discovered by Pasky), and the entries
@ -87,7 +89,7 @@ the stat information from updated paths, `st_mtime` timestamp of
it is usually the same as or newer than any of the paths the
index contains. And no matter how quick the modification that
follows `git update-index foo` finishes, the resulting
`st_mtime` timestamp on `foo` cannot get the timestamp earlier
`st_mtime` timestamp on `foo` cannot get a value earlier
than the index file. Therefore, index entries that can be
racily clean are limited to the ones that have the same
timestamp as the index file itself.
@ -111,7 +113,7 @@ value, and falsely clean entry `foo` would not be caught by the
timestamp comparison check done with the former logic anymore.
The latter makes sure that the cached stat information for `foo`
would never match with the file in the working tree, so later
checks by `ce_match_stat_basic()` would report the index entry
checks by `ce_match_stat_basic()` would report that the index entry
does not match the file and git does not have to fall back on more
expensive `ce_modified_check_fs()`.
@ -155,17 +157,16 @@ of the cached stat information.
Avoiding runtime penalty
------------------------
In order to avoid the above runtime penalty, the recent "master"
branch (post 1.4.2) has a code that makes sure the index file
gets timestamp newer than the youngest files in the index when
In order to avoid the above runtime penalty, post 1.4.2 git used
to have a code that made sure the index file
got timestamp newer than the youngest files in the index when
there are many young files with the same timestamp as the
resulting index file would otherwise would have by waiting
before finishing writing the index file out.
I suspect that in practice the situation where many paths in the
index are all racily clean is quite rare. The only code paths
that can record recent timestamp for large number of paths I
know of are:
I suspected that in practice the situation where many paths in the
index are all racily clean was quite rare. The only code paths
that can record recent timestamp for large number of paths are:
. Initial `git add .` of a large project.
@ -188,6 +189,7 @@ youngest file in the working tree. This means that in these
cases there actually will not be any racily clean entry in
the resulting index.
So in summary I think we should not worry about avoiding the
runtime penalty and get rid of the "wait before finishing
writing" code out.
Based on this discussion, the current code does not use the
"workaround" to avoid the runtime penalty that does not exist in
practice anymore. This was done with commit 0fc82cff on Aug 15,
2006.

@ -796,8 +796,8 @@ test: all
test-date$X: test-date.c date.o ctype.o
$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) test-date.c date.o ctype.o
test-delta$X: test-delta.c diff-delta.o patch-delta.o
$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $^
test-delta$X: test-delta.o diff-delta.o patch-delta.o $(GITLIBS)
$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
test-dump-cache-tree$X: dump-cache-tree.o $(GITLIBS)
$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)

@ -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;
printf("%.*s", (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8, hex);
if (suspect->commit->object.flags & UNINTERESTING) {
length--;
putchar('^');
}
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,

@ -124,8 +124,11 @@ static void copy_templates(const char *git_dir, int len, const char *template_di
int template_len;
DIR *dir;
if (!template_dir)
template_dir = DEFAULT_GIT_TEMPLATE_DIR;
if (!template_dir) {
template_dir = getenv("GIT_TEMPLATE_DIR");
if (!template_dir)
template_dir = DEFAULT_GIT_TEMPLATE_DIR;
}
strcpy(template_path, template_dir);
template_len = strlen(template_path);
if (template_path[template_len-1] != '/') {

@ -3,7 +3,7 @@
#include <regex.h>
static const char git_config_set_usage[] =
"git-repo-config [ --global ] [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --list";
"git-repo-config [ --global ] [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --list";
static char *key;
static regex_t *key_regexp;
@ -148,6 +148,18 @@ int cmd_repo_config(int argc, const char **argv, const char *prefix)
} else {
die("$HOME not set");
}
} else if (!strcmp(argv[1], "--rename-section")) {
int ret;
if (argc != 4)
usage(git_config_set_usage);
ret = git_config_rename_section(argv[2], argv[3]);
if (ret < 0)
return ret;
if (ret == 0) {
fprintf(stderr, "No such section!\n");
return 1;
}
return 0;
} else
break;
argc--;

@ -6,7 +6,7 @@
#include "builtin.h"
static const char show_branch_usage[] =
"git-show-branch [--sparse] [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]";
"git-show-branch [--sparse] [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...] | --reflog[=n] <branch>";
static int default_num;
static int default_alloc;
@ -17,6 +17,8 @@ static const char **default_arg;
#define REV_SHIFT 2
#define MAX_REVS (FLAG_BITS - REV_SHIFT) /* should not exceed bits_per_int - REV_SHIFT */
#define DEFAULT_REFLOG 4
static struct commit *interesting(struct commit_list *list)
{
while (list) {
@ -570,6 +572,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
int head_at = -1;
int topics = 0;
int dense = 1;
int reflog = 0;
git_config(git_show_branch_config);
@ -615,6 +618,15 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
dense = 0;
else if (!strcmp(arg, "--date-order"))
lifo = 0;
else if (!strcmp(arg, "--reflog")) {
reflog = DEFAULT_REFLOG;
}
else if (!strncmp(arg, "--reflog=", 9)) {
char *end;
reflog = strtoul(arg + 9, &end, 10);
if (*end != '\0')
die("unrecognized reflog count '%s'", arg + 9);
}
else
usage(show_branch_usage);
ac--; av++;
@ -622,7 +634,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
ac--; av++;
/* Only one of these is allowed */
if (1 < independent + merge_base + (extra != 0))
if (1 < independent + merge_base + (extra != 0) + (!!reflog))
usage(show_branch_usage);
/* If nothing is specified, show all branches by default */
@ -631,9 +643,22 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
if (all_heads + all_tags)
snarf_refs(all_heads, all_tags);
while (0 < ac) {
append_one_rev(*av);
ac--; av++;
if (reflog) {
int reflen;
if (!ac)
die("--reflog option needs one branch name");
reflen = strlen(*av);
for (i = 0; i < reflog; i++) {
char *name = xmalloc(reflen + 20);
sprintf(name, "%s@{%d}", *av, i);
append_one_rev(name);
}
}
else {
while (0 < ac) {
append_one_rev(*av);
ac--; av++;
}
}
head_p = resolve_ref("HEAD", head_sha1, 1, NULL);

@ -2,13 +2,23 @@
#include "refs.h"
#include "object.h"
#include "tag.h"
#include "path-list.h"
static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash[=<length>]] [--abbrev[=<length>]] [--tags] [--heads] [--] [pattern*]";
static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash[=<length>]] [--abbrev[=<length>]] [--tags] [--heads] [--] [pattern*] < ref-list";
static int deref_tags = 0, show_head = 0, tags_only = 0, heads_only = 0,
found_match = 0, verify = 0, quiet = 0, hash_only = 0, abbrev = 0;
static const char **pattern;
static void show_one(const char *refname, const unsigned char *sha1)
{
const char *hex = find_unique_abbrev(sha1, abbrev);
if (hash_only)
printf("%s\n", hex);
else
printf("%s %s\n", hex, refname);
}
static int show_ref(const char *refname, const unsigned char *sha1, int flag, void *cbdata)
{
struct object *obj;
@ -57,11 +67,7 @@ match:
if (quiet)
return 0;
hex = find_unique_abbrev(sha1, abbrev);
if (hash_only)
printf("%s\n", hex);
else
printf("%s %s\n", hex, refname);
show_one(refname, sha1);
if (!deref_tags)
return 0;
@ -86,6 +92,60 @@ match:
return 0;
}
static int add_existing(const char *refname, const unsigned char *sha1, int flag, void *cbdata)
{
struct path_list *list = (struct path_list *)cbdata;
path_list_insert(refname, list);
return 0;
}
/*
* read "^(?:<anything>\s)?<refname>(?:\^\{\})?$" from the standard input,
* and
* (1) strip "^{}" at the end of line if any;
* (2) ignore if match is provided and does not head-match refname;
* (3) warn if refname is not a well-formed refname and skip;
* (4) ignore if refname is a ref that exists in the local repository;
* (5) otherwise output the line.
*/
static int exclude_existing(const char *match)
{
static struct path_list existing_refs = { NULL, 0, 0, 0 };
char buf[1024];
int matchlen = match ? strlen(match) : 0;
for_each_ref(add_existing, &existing_refs);
while (fgets(buf, sizeof(buf), stdin)) {
char *ref;
int len = strlen(buf);
if (len > 0 && buf[len - 1] == '\n')
buf[--len] = '\0';
if (3 <= len && !strcmp(buf + len - 3, "^{}")) {
len -= 3;
buf[len] = '\0';
}
for (ref = buf + len; buf < ref; ref--)
if (isspace(ref[-1]))
break;
if (match) {
int reflen = buf + len - ref;
if (reflen < matchlen)
continue;
if (strncmp(ref, match, matchlen))
continue;
}
if (check_ref_format(ref)) {
fprintf(stderr, "warning: ref '%s' ignored\n", ref);
continue;
}
if (!path_list_has_path(&existing_refs, ref)) {
printf("%s\n", buf);
}
}
return 0;
}
int cmd_show_ref(int argc, const char **argv, const char *prefix)
{
int i;
@ -121,13 +181,13 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
if (!strncmp(arg, "--hash=", 7) ||
(!strncmp(arg, "--abbrev", 8) &&
(arg[8] == '=' || arg[8] == '\0'))) {
if (arg[3] != 'h' && !arg[8])
if (arg[2] != 'h' && !arg[8])
/* --abbrev only */
abbrev = DEFAULT_ABBREV;
else {
/* --hash= or --abbrev= */
char *end;
if (arg[3] == 'h') {
if (arg[2] == 'h') {
hash_only = 1;
arg += 7;
}
@ -153,8 +213,31 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
heads_only = 1;
continue;
}
if (!strcmp(arg, "--exclude-existing"))
return exclude_existing(NULL);
if (!strncmp(arg, "--exclude-existing=", 19))
return exclude_existing(arg + 19);
usage(show_ref_usage);
}
if (verify) {
unsigned char sha1[20];
while (*pattern) {
if (!strncmp(*pattern, "refs/", 5) &&
resolve_ref(*pattern, sha1, 1, NULL)) {
if (!quiet)
show_one(*pattern, sha1);
}
else if (!quiet)
die("'%s' - not a valid ref", *pattern);
else
return 1;
pattern++;
}
return 0;
}
if (show_head)
head_ref(show_ref, NULL);
for_each_ref(show_ref, NULL);

@ -309,6 +309,7 @@ void datestamp(char *buf, int bufsize);
unsigned long approxidate(const char *);
extern int setup_ident(void);
extern void ignore_missing_committer_name();
extern const char *git_author_info(int);
extern const char *git_committer_info(int);
@ -404,6 +405,7 @@ extern int git_config_int(const char *, const char *);
extern int git_config_bool(const char *, const char *);
extern int git_config_set(const char *, const char *);
extern int git_config_set_multivar(const char *, const char *, const char *, int);
extern int git_config_rename_section(const char *, const char *);
extern int check_repository_format_version(const char *var, const char *value);
#define MAX_GITNAME (1000)

@ -746,4 +746,68 @@ out_free:
return ret;
}
int git_config_rename_section(const char *old_name, const char *new_name)
{
int ret = 0;
const char *config_filename;
struct lock_file *lock = xcalloc(sizeof(struct lock_file), 1);
int out_fd;
char buf[1024];
config_filename = getenv("GIT_CONFIG");
if (!config_filename) {
config_filename = getenv("GIT_CONFIG_LOCAL");
if (!config_filename)
config_filename = git_path("config");
}
config_filename = xstrdup(config_filename);
out_fd = hold_lock_file_for_update(lock, config_filename, 0);
if (out_fd < 0)
return error("Could not lock config file!");
if (!(config_file = fopen(config_filename, "rb")))
return error("Could not open config file!");
while (fgets(buf, sizeof(buf), config_file)) {
int i;
for (i = 0; buf[i] && isspace(buf[i]); i++)
; /* do nothing */
if (buf[i] == '[') {
/* it's a section */
int j = 0, dot = 0;
for (i++; buf[i] && buf[i] != ']'; i++) {
if (!dot && isspace(buf[i])) {
dot = 1;
if (old_name[j++] != '.')
break;
for (i++; isspace(buf[i]); i++)
; /* do nothing */
if (buf[i] != '"')
break;
continue;
}
if (buf[i] == '\\' && dot)
i++;
else if (buf[i] == '"' && dot) {
for (i++; isspace(buf[i]); i++)
; /* do_nothing */
break;
}
if (buf[i] != old_name[j++])
break;
}
if (buf[i] == ']') {
/* old_name matches */
ret++;
store.baselen = strlen(new_name);
store_write_section(out_fd, new_name);
continue;
}
}
write(out_fd, buf, strlen(buf));
}
if (close(out_fd) || commit_lock_file(lock) < 0)
return error("Cannot commit config file!");
return ret;
}

@ -1,7 +1,7 @@
syn region gitLine start=/^#/ end=/$/
syn region gitCommit start=/^# Updated but not checked in:$/ end=/^#$/ contains=gitHead,gitCommitFile
syn region gitCommit start=/^# Added but not yet committed:$/ end=/^#$/ contains=gitHead,gitCommitFile
syn region gitHead contained start=/^# (.*)/ end=/^#$/
syn region gitChanged start=/^# Changed but not updated:/ end=/^#$/ contains=gitHead,gitChangedFile
syn region gitChanged start=/^# Changed but not added:/ end=/^#$/ contains=gitHead,gitChangedFile
syn region gitUntracked start=/^# Untracked files:/ end=/^#$/ contains=gitHead,gitUntrackedFile
syn match gitCommitFile contained /^#\t.*/hs=s+2

@ -96,7 +96,7 @@ fi
# Global that is reused later
ls_remote_result=$(git ls-remote $upload_pack "$remote") ||
die "Cannot find the reflist at $remote"
die "Cannot get the repository state from $remote"
append_fetch_head () {
head_="$1"
@ -242,7 +242,7 @@ esac
reflist=$(get_remote_refs_for_fetch "$@")
if test "$tags"
then
taglist=`IFS=" " &&
taglist=`IFS=' ' &&
echo "$ls_remote_result" |
while read sha1 name
do
@ -438,17 +438,11 @@ case "$no_tags$tags" in
*:refs/*)
# effective only when we are following remote branch
# using local tracking branch.
taglist=$(IFS=" " &&
taglist=$(IFS=' ' &&
echo "$ls_remote_result" |
sed -n -e 's|^\('"$_x40"'\) \(refs/tags/.*\)^{}$|\1 \2|p' \
-e 's|^\('"$_x40"'\) \(refs/tags/.*\)$|\1 \2|p' |
git-show-ref --exclude-existing=refs/tags/ |
while read sha1 name
do
git-show-ref --verify --quiet -- "$name" && continue
git-check-ref-format "$name" || {
echo >&2 "warning: tag ${name} ignored"
continue
}
git-cat-file -t "$sha1" >/dev/null 2>&1 || continue
echo >&2 "Auto-following $name"
echo ".${name}:${name}"

@ -94,7 +94,7 @@ while read sha1 path
do
case "$sha1" in
failed)
die "Failed to find remote refs"
exit 1 ;;
esac
case "$path" in
refs/heads/*)

@ -111,16 +111,14 @@ expand_refs_wildcard () {
local_force=
test "z$lref" = "z$ref" || local_force='+'
echo "$ls_remote_result" |
sed -e '/\^{}$/d' |
(
IFS=' '
while read sha1 name
do
# ignore the ones that do not start with $from
mapped=${name#"$from"}
if test "z$name" != "z${name%'^{}'}" ||
test "z$name" = "z$mapped"
then
continue
fi
test "z$name" = "z$mapped" && continue
echo "${local_force}${name}:${to}${mapped}"
done
)

@ -154,7 +154,7 @@ sub find_conflict {
sub merge {
my ($name, $path) = @_;
record_preimage($path, "$rr_dir/$name/thisimage");
unless (system('git merge-file', 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

@ -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,
@ -827,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;
}
@ -856,7 +855,8 @@ sub format_ref_marker {
$name = $ref;
}
$markers .= " <span class=\"$type\">" . esc_html($name) . "</span>";
$markers .= " <span class=\"$type\" title=\"$ref\">" .
esc_html($name) . "</span>";
}
}
@ -1989,12 +1989,73 @@ 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;
}
# 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 <tr> 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 +2066,31 @@ sub git_print_tree_entry {
print "<td class=\"list\">" .
$cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
file_name=>"$basedir$t->{'name'}", %base_key),
-class => "list"}, esc_path($t->{'name'})) . "</td>\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) {
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 "</td>\n";
print "<td class=\"link\">";
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 +2112,8 @@ sub git_print_tree_entry {
print "</td>\n";
print "<td class=\"link\">";
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,
@ -3414,8 +3490,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');
@ -3497,15 +3572,46 @@ sub git_commit {
my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'});
my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'});
my $parent = $co{'parent'};
my $parent = $co{'parent'};
my $parents = $co{'parents'}; # listref
# we need to prepare $formats_nav before any parameter munging
my $formats_nav;
if (!defined $parent) {
# --root commitdiff
$formats_nav .= '(initial)';
} elsif (@$parents == 1) {
# single parent commit
$formats_nav .=
'(parent: ' .
$cgi->a({-href => href(action=>"commit",
hash=>$parent)},
esc_html(substr($parent, 0, 7))) .
')';
} else {
# merge commit
$formats_nav .=
'(merge: ' .
join(' ', map {
$cgi->a({-href => href(action=>"commitdiff",
hash=>$_)},
esc_html(substr($_, 0, 7)));
} @$parents ) .
')';
}
if (!defined $parent) {
$parent = "--root";
}
open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id",
@diff_opts, $parent, $hash, "--"
or die_error(undef, "Open git-diff-tree failed");
my @difftree = map { chomp; $_ } <$fd>;
close $fd or die_error(undef, "Reading git-diff-tree failed");
my @difftree;
if (@$parents <= 1) {
# difftree output is not printed for merges
open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id",
@diff_opts, $parent, $hash, "--"
or die_error(undef, "Open git-diff-tree failed");
@difftree = map { chomp; $_ } <$fd>;
close $fd or die_error(undef, "Reading git-diff-tree failed");
}
# non-textual hash id's can be cached
my $expires;
@ -3517,16 +3623,10 @@ sub git_commit {
my $have_snapshot = gitweb_have_snapshot();
my @views_nav = ();
if (defined $file_name && defined $co{'parent'}) {
push @views_nav,
$cgi->a({-href => href(action=>"blame", hash_parent=>$parent, file_name=>$file_name)},
"blame");
}
git_header_html(undef, $expires);
git_print_page_nav('commit', '',
$hash, $co{'tree'}, $hash,
join (' | ', @views_nav));
$formats_nav);
if (defined $co{'parent'}) {
git_print_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash);
@ -3567,7 +3667,7 @@ sub git_commit {
}
print "</td>" .
"</tr>\n";
my $parents = $co{'parents'};
foreach my $par (@$parents) {
print "<tr>" .
"<td>parent</td>" .
@ -3589,11 +3689,61 @@ sub git_commit {
git_print_log($co{'comment'});
print "</div>\n";
git_difftree_body(\@difftree, $hash, $parent);
if (@$parents <= 1) {
# do not output difftree/whatchanged for merges
git_difftree_body(\@difftree, $hash, $parent);
}
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';

15
ident.c

@ -221,3 +221,18 @@ const char *git_committer_info(int error_on_no_name)
getenv("GIT_COMMITTER_DATE"),
error_on_no_name);
}
void ignore_missing_committer_name()
{
/* If we did not get a name from the user's gecos entry then
* git_default_name is empty; so instead load the username
* into it as a 'good enough for now' approximation of who
* this user is.
*/
if (!*git_default_name) {
struct passwd *pw = getpwuid(getuid());
if (!pw)
die("You don't exist. Go away!");
strlcpy(git_default_name, pw->pw_name, sizeof(git_default_name));
}
}

@ -9,8 +9,7 @@
* published by the Free Software Foundation.
*/
#include <stdlib.h>
#include <string.h>
#include "git-compat-util.h"
#include "delta.h"
void *patch_delta(const void *src_buf, unsigned long src_size,
@ -34,9 +33,7 @@ void *patch_delta(const void *src_buf, unsigned long src_size,
/* now the result size */
size = get_delta_hdr_size(&data, top);
dst_buf = malloc(size + 1);
if (!dst_buf)
return NULL;
dst_buf = xmalloc(size + 1);
dst_buf[size] = 0;
out = dst_buf;
@ -55,13 +52,13 @@ void *patch_delta(const void *src_buf, unsigned long src_size,
if (cp_off + cp_size < cp_size ||
cp_off + cp_size > src_size ||
cp_size > size)
goto bad;
break;
memcpy(out, (char *) src_buf + cp_off, cp_size);
out += cp_size;
size -= cp_size;
} else if (cmd) {
if (cmd > size)
goto bad;
break;
memcpy(out, data, cmd);
out += cmd;
data += cmd;
@ -72,12 +69,14 @@ void *patch_delta(const void *src_buf, unsigned long src_size,
* extensions. In the mean time we must fail when
* encountering them (might be data corruption).
*/
error("unexpected delta opcode 0");
goto bad;
}
}
/* sanity check */
if (data != top || size != 0) {
error("delta replay has gone wild");
bad:
free(dst_buf);
return NULL;

@ -72,7 +72,7 @@ static void safe_read(int fd, void *buffer, unsigned size)
if (ret < 0)
die("read error (%s)", strerror(errno));
if (!ret)
die("unexpected EOF");
die("The remote end hung up unexpectedly");
n += ret;
}
}

@ -358,7 +358,7 @@ int add_file_to_index(const char *path, int verbose)
if (index_path(ce->sha1, path, &st, 1))
die("unable to index file %s", path);
if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD))
if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE))
die("unable to add %s to index",path);
if (verbose)
printf("add '%s'\n", path);
@ -517,7 +517,7 @@ static int has_dir_name(const struct cache_entry *ce, int pos, int ok_to_replace
pos = cache_name_pos(name, ntohs(create_ce_flags(len, stage)));
if (pos >= 0) {
retval = -1;
if (ok_to_replace)
if (!ok_to_replace)
break;
remove_cache_entry_at(pos);
continue;
@ -609,7 +609,7 @@ int add_cache_entry(struct cache_entry *ce, int option)
if (!skip_df_check &&
check_file_directory_conflict(ce, pos, ok_to_replace)) {
if (!ok_to_replace)
return -1;
return error("'%s' appears as both a file and as a directory", ce->name);
pos = cache_name_pos(ce->name, ntohs(ce->ce_flags));
pos = -pos-1;
}

@ -420,6 +420,8 @@ int main(int argc, char **argv)
die("'%s': unable to chdir or not a git archive", dir);
setup_ident();
/* don't die if gecos is empty */
ignore_missing_committer_name();
git_config(receive_pack_config);
write_head_info();

10
refs.c

@ -867,6 +867,16 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
goto rollback;
}
if (!strncmp(oldref, "refs/heads/", 11) &&
!strncmp(newref, "refs/heads/", 11)) {
char oldsection[1024], newsection[1024];
snprintf(oldsection, 1024, "branch.%s", oldref + 11);
snprintf(newsection, 1024, "branch.%s", newref + 11);
if (git_config_rename_section(oldsection, newsection) < 0)
return 1;
}
return 0;
rollback:

@ -272,4 +272,13 @@ test_expect_success \
wc -l) &&
test $numparent = 1'
test_expect_success 'update-index D/F conflict' '
mv path0 tmp &&
mv path2 path0 &&
mv tmp path2 &&
git update-index --add --replace path2 path0/file2 &&
numpath0=$(git ls-files path0 | wc -l) &&
test $numpath0 = 1
'
test_done

@ -343,5 +343,53 @@ EOF
test_expect_success '--set in alternative GIT_CONFIG' 'cmp other-config expect'
cat > .git/config << EOF
# Hallo
#Bello
[branch "eins"]
x = 1
[branch.eins]
y = 1
[branch "1 234 blabl/a"]
weird
EOF
test_expect_success "rename section" \
"git-repo-config --rename-section branch.eins branch.zwei"
cat > expect << EOF
# Hallo
#Bello
[branch "zwei"]
x = 1
[branch "zwei"]
y = 1
[branch "1 234 blabl/a"]
weird
EOF
test_expect_success "rename succeeded" "diff -u expect .git/config"
test_expect_failure "rename non-existing section" \
'git-repo-config --rename-section branch."world domination" branch.drei'
test_expect_success "rename succeeded" "diff -u expect .git/config"
test_expect_success "rename another section" \
'git-repo-config --rename-section branch."1 234 blabl/a" branch.drei'
cat > expect << EOF
# Hallo
#Bello
[branch "zwei"]
x = 1
[branch "zwei"]
y = 1
[branch "drei"]
weird
EOF
test_expect_success "rename succeeded" "diff -u expect .git/config"
test_done

@ -94,6 +94,8 @@ test_expect_failure \
git-branch r &&
git-branch -m q r/q'
git-repo-config branch.s/s.dummy Hello
test_expect_success \
'git branch -m s/s s should work when s/t is deleted' \
'git-branch -l s/s &&
@ -104,6 +106,10 @@ test_expect_success \
git-branch -m s/s s &&
test -f .git/logs/refs/heads/s'
test_expect_success 'config information was renamed, too' \
"test $(git-repo-config branch.s.dummy) = Hello &&
! git-repo-config branch.s/s/dummy"
test_expect_failure \
'git-branch -m u v should fail when the reflog for u is a symlink' \
'git-branch -l u &&

@ -208,8 +208,9 @@ test_done () {
# t/ subdirectory and are run in trash subdirectory.
PATH=$(pwd)/..:$PATH
GIT_EXEC_PATH=$(pwd)/..
GIT_TEMPLATE_DIR=$(pwd)/../templates/blt
HOME=$(pwd)/trash
export PATH GIT_EXEC_PATH HOME
export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR HOME
GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git
export GITPERLLIB