[verse]
'git-clone' [--template=<template_directory>] [-l [-s]] [-q] [-n] [--bare]
[-o <name>] [-u <upload-pack>] [--reference <repository>]
- [--use-separate-remote] <repository> [<directory>]
+ [--use-separate-remote | --use-immingled-remote] <repository>
+ [<directory>]
DESCRIPTION
-----------
Make a 'bare' GIT repository. That is, instead of
creating `<directory>` and placing the administrative
files in `<directory>/.git`, make the `<directory>`
- itself the `$GIT_DIR`. This implies `-n` option. When
- this option is used, neither the `origin` branch nor the
- default `remotes/origin` file is created.
+ itself the `$GIT_DIR`. This obviously implies the `-n`
+ because there is nowhere to check out the working tree.
+ Also the branch heads at the remote are copied directly
+ to corresponding local branch heads, without mapping
+ them to `refs/remotes/origin/`. When this option is
+ used, neither the `origin` branch nor the default
+ `remotes/origin` file is created.
--origin <name>::
-o <name>::
--use-separate-remote::
Save remotes heads under `$GIT_DIR/remotes/origin/` instead
- of `$GIT_DIR/refs/heads/`. Only the master branch is saved
- in the latter.
+ of `$GIT_DIR/refs/heads/`. Only the local master branch is
+ saved in the latter. This is the default.
+
+--use-immingled-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
+ older Git versions, and will be removed before the next
+ major release.
<repository>::
The (possibly remote) repository to clone from. It can
This flag causes "git-diff-tree --stdin" to also show
the commit message before the differences.
---pretty[=(raw|medium|short)]::
- This is used to control "pretty printing" format of the
- commit message. Without "=<style>", it defaults to
- medium.
+include::pretty-formats.txt[]
--no-commit-id::
git-diff-tree outputs a line with the commit ID when
OPTIONS
-------
---pretty=<format>::
- Controls the way the commit log is formatted.
+
+include::pretty-formats.txt[]
--max-count=<n>::
Limits the number of commits to show.
more specialized family of commit log tools: gitlink:git-log[1],
gitlink:git-show[1], and gitlink:git-whatchanged[1]
---pretty[='<format>']::
-
- Pretty print the contents of the commit logs in a given format,
- where '<format>' can be one of 'raw', 'medium', 'short', 'full',
- and 'oneline'. When left out the format default to 'medium'.
+include::pretty-formats.txt[]
--relative-date::
<commitid>::
ID of the commit to show.
---pretty=<format>::
- Controls the output format for the commit logs.
- <format> can be one of 'raw', 'medium', 'short', 'full',
- and 'oneline'.
+include::pretty-formats.txt[]
Author
------
--- /dev/null
+--pretty[='<format>']::
+
+ Pretty-prints the details of a commit. `--pretty`
+ without an explicit `=<format>` defaults to 'medium'.
+ If the commit is a merge, and if the pretty-format
+ is not 'oneline', 'email' or 'raw', an additional line is
+ inserted before the 'Author:' line. This line begins with
+ "Merge: " and the sha1s of ancestral commits are printed,
+ separated by spaces. Note that the listed commits may not
+ necessarily be the list of the *direct* parent commits if you
+ have limited your view of history: for example, if you are
+ only interested in changes related to a certain directory or
+ file. Here are some additional details for each format:
+
+ * 'oneline'
+
+ <sha1> <title line>
++
+This is designed to be as compact as possible.
+
+ * 'short'
+
+ commit <sha1>
+ Author: <author>
+
+ <title line>
+
+ * 'medium'
+
+ commit <sha1>
+ Author: <author>
+ Date: <date>
+
+ <title line>
+
+ <full commit message>
+
+ * 'full'
+
+ commit <sha1>
+ Author: <author>
+ Commit: <committer>
+
+ <title line>
+
+ <full commit message>
+
+ * 'fuller'
+
+ commit <sha1>
+ Author: <author>
+ AuthorDate: <date & time>
+ Commit: <committer>
+ CommitDate: <date & time>
+
+ <title line>
+
+ <full commit message>
+
+
+ * 'email'
+
+ From <sha1> <date>
+ From: <author>
+ Date: <date & time>
+ Subject: [PATCH] <title line>
+
+ full commit message>
+
+
+ * 'raw'
++
+The 'raw' format shows the entire commit exactly as
+stored in the commit object. Notably, the SHA1s are
+displayed in full, regardless of whether --abbrev or
+--no-abbrev are used, and 'parents' information show the
+true parent commits, without taking grafts nor history
+simplification into account.
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v1.4.4.GIT
+DEF_VER=v1.4.4.1.GIT
LF='
'
unsigned char size[4];
unsigned char filename_length[2];
unsigned char extra_length[2];
+ unsigned char _end[1];
};
struct zip_dir_header {
unsigned char attr1[2];
unsigned char attr2[4];
unsigned char offset[4];
+ unsigned char _end[1];
};
struct zip_dir_trailer {
unsigned char size[4];
unsigned char offset[4];
unsigned char comment_length[2];
+ unsigned char _end[1];
};
+/*
+ * On ARM, padding is added at the end of the struct, so a simple
+ * sizeof(struct ...) reports two bytes more than the payload size
+ * we're interested in.
+ */
+#define ZIP_LOCAL_HEADER_SIZE offsetof(struct zip_local_header, _end)
+#define ZIP_DIR_HEADER_SIZE offsetof(struct zip_dir_header, _end)
+#define ZIP_DIR_TRAILER_SIZE offsetof(struct zip_dir_trailer, _end)
+
static void copy_le16(unsigned char *dest, unsigned int n)
{
dest[0] = 0xff & n;
}
/* make sure we have enough free space in the dictionary */
- direntsize = sizeof(struct zip_dir_header) + pathlen;
+ direntsize = ZIP_DIR_HEADER_SIZE + pathlen;
while (zip_dir_size < zip_dir_offset + direntsize) {
zip_dir_size += ZIP_DIRECTORY_MIN_SIZE;
zip_dir = xrealloc(zip_dir, zip_dir_size);
copy_le16(dirent.attr1, 0);
copy_le32(dirent.attr2, attr2);
copy_le32(dirent.offset, zip_offset);
- memcpy(zip_dir + zip_dir_offset, &dirent, sizeof(struct zip_dir_header));
- zip_dir_offset += sizeof(struct zip_dir_header);
+ memcpy(zip_dir + zip_dir_offset, &dirent, ZIP_DIR_HEADER_SIZE);
+ zip_dir_offset += ZIP_DIR_HEADER_SIZE;
memcpy(zip_dir + zip_dir_offset, path, pathlen);
zip_dir_offset += pathlen;
zip_dir_entries++;
copy_le32(header.size, uncompressed_size);
copy_le16(header.filename_length, pathlen);
copy_le16(header.extra_length, 0);
- write_or_die(1, &header, sizeof(struct zip_local_header));
- zip_offset += sizeof(struct zip_local_header);
+ write_or_die(1, &header, ZIP_LOCAL_HEADER_SIZE);
+ zip_offset += ZIP_LOCAL_HEADER_SIZE;
write_or_die(1, path, pathlen);
zip_offset += pathlen;
if (compressed_size > 0) {
copy_le16(trailer.comment_length, sha1 ? 40 : 0);
write_or_die(1, zip_dir, zip_dir_offset);
- write_or_die(1, &trailer, sizeof(struct zip_dir_trailer));
+ write_or_die(1, &trailer, ZIP_DIR_TRAILER_SIZE);
if (sha1)
write_or_die(1, sha1_to_hex(sha1), 40);
}
ce->ce_mode = create_ce_mode(st.st_mode);
if (!trust_executable_bit) {
/* If there is an existing entry, pick the mode bits
- * from it, otherwise force to 644.
+ * from it, otherwise assume unexecutable.
*/
int pos = cache_name_pos(path, namelen);
if (0 <= pos)
ce->ce_mode = active_cache[pos]->ce_mode;
- else
- ce->ce_mode = create_ce_mode(S_IFREG | 0644);
+ else if (S_ISREG(st.st_mode))
+ ce->ce_mode = create_ce_mode(S_IFREG | 0666);
}
if (index_path(ce->sha1, path, &st, !info_only))
struct ref *refs,
struct ref **matched_ref)
{
- int match;
int patlen = strlen(pattern);
+ struct ref *matched_weak = NULL;
+ struct ref *matched = NULL;
+ int weak_match = 0;
+ int match = 0;
- for (match = 0; refs; refs = refs->next) {
+ for (weak_match = match = 0; refs; refs = refs->next) {
char *name = refs->name;
int namelen = strlen(name);
+ int weak_match;
+
if (namelen < patlen ||
memcmp(name + namelen - patlen, pattern, patlen))
continue;
if (namelen != patlen && name[namelen - patlen - 1] != '/')
continue;
- match++;
- *matched_ref = refs;
+
+ /* A match is "weak" if it is with refs outside
+ * heads or tags, and did not specify the pattern
+ * in full (e.g. "refs/remotes/origin/master") or at
+ * least from the toplevel (e.g. "remotes/origin/master");
+ * otherwise "git push $URL master" would result in
+ * ambiguity between remotes/origin/master and heads/master
+ * at the remote site.
+ */
+ if (namelen != patlen &&
+ patlen != namelen - 5 &&
+ strncmp(name, "refs/heads/", 11) &&
+ strncmp(name, "refs/tags/", 10)) {
+ /* We want to catch the case where only weak
+ * matches are found and there are multiple
+ * matches, and where more than one strong
+ * matches are found, as ambiguous. One
+ * strong match with zero or more weak matches
+ * are acceptable as a unique match.
+ */
+ matched_weak = refs;
+ weak_match++;
+ }
+ else {
+ matched = refs;
+ match++;
+ }
+ }
+ if (!matched) {
+ *matched_ref = matched_weak;
+ return weak_match;
+ }
+ else {
+ *matched_ref = matched;
+ return match;
}
- return match;
}
static void link_dst_tail(struct ref *ref, struct ref ***tail)
}
usage() {
- die "Usage: $0 [--template=<template_directory>] [--use-separate-remote] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [-n] <repo> [<dir>]"
+ die "Usage: $0 [--template=<template_directory>] [--use-immingled-remote] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [-n] <repo> [<dir>]"
}
get_repo_base() {
reference=
origin=
origin_override=
-use_separate_remote=
+use_separate_remote=t
while
case "$#,$1" in
0,*) break ;;
template="$1" ;;
*,-q|*,--quiet) quiet=-q ;;
*,--use-separate-remote)
+ # default
use_separate_remote=t ;;
+ *,--use-immingled-remote)
+ use_separate_remote= ;;
1,--reference) usage ;;
*,--reference)
shift; reference="$1" ;;
test -n "$repo" ||
die 'you must specify a repository to clone.'
-# --bare implies --no-checkout
+# --bare implies --no-checkout and --use-immingled-remote
if test yes = "$bare"
then
if test yes = "$origin_override"
then
die '--bare and --origin $origin options are incompatible.'
fi
- if test t = "$use_separate_remote"
- then
- die '--bare and --use-separate-remote options are incompatible.'
- fi
no_checkout=yes
+ use_separate_remote=
fi
if test -z "$origin"
sub conn {
my $self = shift;
my $repo = $self->{'fullrep'};
- if($repo =~ s/^:pserver:(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?//) {
- my($user,$pass,$serv,$port) = ($1,$2,$3,$4);
+ if($repo =~ s/^:pserver(?:([^:]*)):(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?//) {
+ my($param,$user,$pass,$serv,$port) = ($1,$2,$3,$4,$5);
+
+ my($proxyhost,$proxyport);
+ if($param && ($param =~ m/proxy=([^;]+)/)) {
+ $proxyhost = $1;
+ # Default proxyport, if not specified, is 8080.
+ $proxyport = 8080;
+ if($ENV{"CVS_PROXY_PORT"}) {
+ $proxyport = $ENV{"CVS_PROXY_PORT"};
+ }
+ if($param =~ m/proxyport=([^;]+)/){
+ $proxyport = $1;
+ }
+ }
+
$user="anonymous" unless defined $user;
my $rr2 = "-";
unless($port) {
}
$pass="A" unless $pass;
- my $s = IO::Socket::INET->new(PeerHost => $serv, PeerPort => $port);
- die "Socket to $serv: $!\n" unless defined $s;
+ my ($s, $rep);
+ if($proxyhost) {
+
+ # Use a HTTP Proxy. Only works for HTTP proxies that
+ # don't require user authentication
+ #
+ # See: http://www.ietf.org/rfc/rfc2817.txt
+
+ $s = IO::Socket::INET->new(PeerHost => $proxyhost, PeerPort => $proxyport);
+ die "Socket to $proxyhost: $!\n" unless defined $s;
+ $s->write("CONNECT $serv:$port HTTP/1.1\r\nHost: $serv:$port\r\n\r\n")
+ or die "Write to $proxyhost: $!\n";
+ $s->flush();
+
+ $rep = <$s>;
+
+ # The answer should look like 'HTTP/1.x 2yy ....'
+ 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
+ # including the response headers.
+ while ($rep = <$s>) {
+ last if (!defined $rep ||
+ $rep eq "\n" ||
+ $rep eq "\r\n");
+ }
+ } else {
+ $s = IO::Socket::INET->new(PeerHost => $serv, PeerPort => $port);
+ die "Socket to $serv: $!\n" unless defined $s;
+ }
+
$s->write("BEGIN AUTH REQUEST\n$repo\n$user\n$pass\nEND AUTH REQUEST\n")
or die "Write to $serv: $!\n";
$s->flush();
- my $rep = <$s>;
+ $rep = <$s>;
if($rep ne "I LOVE YOU\n") {
$rep="<unknown>" unless $rep;
memoize('cmt_metadata');
memoize('get_commit_time');
-my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib);
+my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib, $AUTH_BATON, $AUTH_CALLBACKS);
sub nag_lib {
print STDERR <<EOF;
$_template, $_shared, $_no_default_regex, $_no_graft_copy,
$_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit,
$_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m,
- $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive);
+ $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive,
+ $_username, $_config_dir, $_no_auth_cache);
my (@_branch_from, %tree_map, %users, %rusers, %equiv);
my ($_svn_co_url_revs, $_svn_pg_peg_revs);
my @repo_path_split_cache;
'repack:i' => \$_repack,
'no-metadata' => \$_no_metadata,
'quiet|q' => \$_q,
+ 'username=s' => \$_username,
+ 'config-dir=s' => \$_config_dir,
+ 'no-auth-cache' => \$_no_auth_cache,
'ignore-nodate' => \$_ignore_nodate,
'repack-flags|repack-args|repack-opts=s' => \$_repack_flags);
my @commit = grep(/^git-svn-id: /,`git-cat-file commit $c`);
next if (!@commit); # skip merges
my ($url, $rev, $uuid) = extract_metadata($commit[$#commit]);
- if (!$rev || !$uuid) {
+ if (!defined $rev || !$uuid) {
croak "Unable to extract revision or UUID from ",
"$c, $commit[$#commit]\n";
}
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) {
+ die "Commit $d\n",
+ "has no parent commit, and therefore ",
+ "nothing to diff against.\n",
+ "You should be working from a repository ",
+ "originally created by git-svn\n";
+ }
unless (defined $last_rev) {
(undef, $last_rev, undef) = cmt_metadata("$d~1");
unless (defined $last_rev) {
} else {
print "No changes between current HEAD and $gs\n",
"Hard resetting to the latest $gs\n";
- @finish = qw/reset --hard/;
+ @finish = qw/reset --mixed/;
}
sys('git', @finish, $gs);
}
print STDERR "Needed URL or usable git-svn id command-line\n";
commit_diff_usage();
}
- my $r = shift || $_revision;
- die "-r|--revision is a required argument\n" unless (defined $r);
+ my $r = shift;
+ unless (defined $r) {
+ if (defined $_revision) {
+ $r = $_revision
+ } else {
+ die "-r|--revision is a required argument\n";
+ }
+ }
if (defined $_message && defined $_file) {
print STDERR "Both --message/-m and --file/-F specified ",
"for the commit message.\n",
my $id = shift or return (undef, undef, undef);
my ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\s(\S+?)\@(\d+)
\s([a-f\d\-]+)$/x);
- if (!$rev || !$uuid || !$url) {
+ if (!defined $rev || !$uuid || !$url) {
# some of the original repositories I made had
# identifiers like this:
($rev, $uuid) = ($id =~/^git-svn-id:\s(\d+)\@([a-f\d\-]+)/);
my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file.
$SVN::Node::dir.$SVN::Node::unknown.
$SVN::Node::none.$SVN::Node::file.
- $SVN::Node::dir.$SVN::Node::unknown;
+ $SVN::Node::dir.$SVN::Node::unknown.
+ $SVN::Auth::SSL::CNMISMATCH.
+ $SVN::Auth::SSL::NOTYETVALID.
+ $SVN::Auth::SSL::EXPIRED.
+ $SVN::Auth::SSL::UNKNOWNCA.
+ $SVN::Auth::SSL::OTHER;
1;
};
}
+sub _simple_prompt {
+ my ($cred, $realm, $default_username, $may_save, $pool) = @_;
+ $may_save = undef if $_no_auth_cache;
+ $default_username = $_username if defined $_username;
+ if (defined $default_username && length $default_username) {
+ if (defined $realm && length $realm) {
+ print "Authentication realm: $realm\n";
+ }
+ $cred->username($default_username);
+ } else {
+ _username_prompt($cred, $realm, $may_save, $pool);
+ }
+ $cred->password(_read_password("Password for '" .
+ $cred->username . "': ", $realm));
+ $cred->may_save($may_save);
+ $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub _ssl_server_trust_prompt {
+ my ($cred, $realm, $failures, $cert_info, $may_save, $pool) = @_;
+ $may_save = undef if $_no_auth_cache;
+ print "Error validating server certificate for '$realm':\n";
+ if ($failures & $SVN::Auth::SSL::UNKNOWNCA) {
+ print " - The certificate is not issued by a trusted ",
+ "authority. Use the\n",
+ " fingerprint to validate the certificate manually!\n";
+ }
+ if ($failures & $SVN::Auth::SSL::CNMISMATCH) {
+ print " - The certificate hostname does not match.\n";
+ }
+ if ($failures & $SVN::Auth::SSL::NOTYETVALID) {
+ print " - The certificate is not yet valid.\n";
+ }
+ if ($failures & $SVN::Auth::SSL::EXPIRED) {
+ print " - The certificate has expired.\n";
+ }
+ if ($failures & $SVN::Auth::SSL::OTHER) {
+ print " - The certificate has an unknown error.\n";
+ }
+ printf( "Certificate information:\n".
+ " - Hostname: %s\n".
+ " - Valid: from %s until %s\n".
+ " - Issuer: %s\n".
+ " - Fingerprint: %s\n",
+ map $cert_info->$_, qw(hostname valid_from valid_until
+ issuer_dname fingerprint) );
+ my $choice;
+prompt:
+ print $may_save ?
+ "(R)eject, accept (t)emporarily or accept (p)ermanently? " :
+ "(R)eject or accept (t)emporarily? ";
+ $choice = lc(substr(<STDIN> || 'R', 0, 1));
+ if ($choice =~ /^t$/i) {
+ $cred->may_save(undef);
+ } elsif ($choice =~ /^r$/i) {
+ return -1;
+ } elsif ($may_save && $choice =~ /^p$/i) {
+ $cred->may_save($may_save);
+ } else {
+ goto prompt;
+ }
+ $cred->accepted_failures($failures);
+ $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub _ssl_client_cert_prompt {
+ my ($cred, $realm, $may_save, $pool) = @_;
+ $may_save = undef if $_no_auth_cache;
+ print "Client certificate filename: ";
+ chomp(my $filename = <STDIN>);
+ $cred->cert_file($filename);
+ $cred->may_save($may_save);
+ $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub _ssl_client_cert_pw_prompt {
+ my ($cred, $realm, $may_save, $pool) = @_;
+ $may_save = undef if $_no_auth_cache;
+ $cred->password(_read_password("Password: ", $realm));
+ $cred->may_save($may_save);
+ $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub _username_prompt {
+ my ($cred, $realm, $may_save, $pool) = @_;
+ $may_save = undef if $_no_auth_cache;
+ if (defined $realm && length $realm) {
+ print "Authentication realm: $realm\n";
+ }
+ my $username;
+ if (defined $_username) {
+ $username = $_username;
+ } else {
+ print "Username: ";
+ chomp($username = <STDIN>);
+ }
+ $cred->username($username);
+ $cred->may_save($may_save);
+ $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub _read_password {
+ my ($prompt, $realm) = @_;
+ print $prompt;
+ require Term::ReadKey;
+ Term::ReadKey::ReadMode('noecho');
+ my $password = '';
+ while (defined(my $key = Term::ReadKey::ReadKey(0))) {
+ last if $key =~ /[\012\015]/; # \n\r
+ $password .= $key;
+ }
+ Term::ReadKey::ReadMode('restore');
+ print "\n";
+ $password;
+}
+
sub libsvn_connect {
my ($url) = @_;
- my $auth = SVN::Core::auth_open([SVN::Client::get_simple_provider(),
- SVN::Client::get_ssl_server_trust_file_provider(),
- SVN::Client::get_username_provider()]);
- my $s = eval { SVN::Ra->new(url => $url, auth => $auth) };
- return $s;
+ 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);
}
sub libsvn_get_file {
ce->ce_mode = create_ce_mode(st.st_mode);
if (!trust_executable_bit) {
/* If there is an existing entry, pick the mode bits
- * from it, otherwise force to 644.
+ * from it, otherwise assume unexecutable.
*/
int pos = cache_name_pos(path, namelen);
if (pos >= 0)
ce->ce_mode = active_cache[pos]->ce_mode;
- else
- ce->ce_mode = create_ce_mode(S_IFREG | 0644);
+ else if (S_ISREG(st.st_mode))
+ ce->ce_mode = create_ce_mode(S_IFREG | 0666);
}
if (index_path(ce->sha1, path, &st, 1))
git-add xfoo1 &&
case "`git-ls-files --stage xfoo1`" in
100644" "*xfoo1) echo ok;;
- *) echo fail; git-ls-files --stage xfoo1; exit 1;;
+ *) echo fail; git-ls-files --stage xfoo1; (exit 1);;
esac'
test_expect_success \
git-update-index --add xfoo2 &&
case "`git-ls-files --stage xfoo2`" in
100644" "*xfoo2) echo ok;;
- *) echo fail; git-ls-files --stage xfoo2; exit 1;;
+ *) echo fail; git-ls-files --stage xfoo2; (exit 1);;
+ esac'
+
+test_expect_success \
+ 'git-update-index --add: Test that executable bit is not used...' \
+ 'git repo-config core.filemode 0 &&
+ ln -s xfoo2 xfoo3 &&
+ git-update-index --add xfoo3 &&
+ case "`git-ls-files --stage xfoo3`" in
+ 120000" "*xfoo3) echo ok;;
+ *) echo fail; git-ls-files --stage xfoo3; (exit 1);;
esac'
test_done
static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] <dir>";
-#define THEY_HAVE (1U << 0)
-#define OUR_REF (1U << 1)
-#define WANTED (1U << 2)
+/* bits #0..7 in revision.h, #8..10 in commit.c */
+#define THEY_HAVE (1u << 11)
+#define OUR_REF (1u << 12)
+#define WANTED (1u << 13)
+#define COMMON_KNOWN (1u << 14)
+#define REACHABLE (1u << 15)
+
+static unsigned long oldest_have;
+
static int multi_ack, nr_our_refs;
static int use_thin_pack, use_ofs_delta;
static struct object_array have_obj;
static int got_sha1(char *hex, unsigned char *sha1)
{
struct object *o;
+ int we_knew_they_have = 0;
if (get_sha1_hex(hex, sha1))
die("git-upload-pack: expected SHA1 object, got '%s'", hex);
if (!has_sha1_file(sha1))
- return 0;
+ return -1;
o = lookup_object(sha1);
if (!(o && o->parsed))
die("oops (%s)", sha1_to_hex(sha1));
if (o->type == OBJ_COMMIT) {
struct commit_list *parents;
+ struct commit *commit = (struct commit *)o;
if (o->flags & THEY_HAVE)
- return 0;
- o->flags |= THEY_HAVE;
- for (parents = ((struct commit*)o)->parents;
+ we_knew_they_have = 1;
+ else
+ o->flags |= THEY_HAVE;
+ if (!oldest_have || (commit->date < oldest_have))
+ oldest_have = commit->date;
+ for (parents = commit->parents;
parents;
parents = parents->next)
parents->item->object.flags |= THEY_HAVE;
}
- add_object_array(o, NULL, &have_obj);
+ if (!we_knew_they_have) {
+ add_object_array(o, NULL, &have_obj);
+ return 1;
+ }
+ return 0;
+}
+
+static int reachable(struct commit *want)
+{
+ struct commit_list *work = NULL;
+
+ insert_by_date(want, &work);
+ while (work) {
+ struct commit_list *list = work->next;
+ struct commit *commit = work->item;
+ free(work);
+ work = list;
+
+ if (commit->object.flags & THEY_HAVE) {
+ want->object.flags |= COMMON_KNOWN;
+ break;
+ }
+ if (!commit->object.parsed)
+ parse_object(commit->object.sha1);
+ if (commit->object.flags & REACHABLE)
+ continue;
+ commit->object.flags |= REACHABLE;
+ if (commit->date < oldest_have)
+ continue;
+ for (list = commit->parents; list; list = list->next) {
+ struct commit *parent = list->item;
+ if (!(parent->object.flags & REACHABLE))
+ insert_by_date(parent, &work);
+ }
+ }
+ want->object.flags |= REACHABLE;
+ clear_commit_marks(want, REACHABLE);
+ free_commit_list(work);
+ return (want->object.flags & COMMON_KNOWN);
+}
+
+static int ok_to_give_up(void)
+{
+ int i;
+
+ if (!have_obj.nr)
+ return 0;
+
+ for (i = 0; i < want_obj.nr; i++) {
+ struct object *want = want_obj.objects[i].item;
+
+ if (want->flags & COMMON_KNOWN)
+ continue;
+ want = deref_tag(want, "a want line", 0);
+ if (!want || want->type != OBJ_COMMIT) {
+ /* no way to tell if this is reachable by
+ * looking at the ancestry chain alone, so
+ * leave a note to ourselves not to worry about
+ * this object anymore.
+ */
+ want_obj.objects[i].item->flags |= COMMON_KNOWN;
+ continue;
+ }
+ if (!reachable((struct commit *)want))
+ return 0;
+ }
return 1;
}
}
len = strip(line, len);
if (!strncmp(line, "have ", 5)) {
- if (got_sha1(line+5, sha1)) {
+ switch (got_sha1(line+5, sha1)) {
+ case -1: /* they have what we do not */
+ if (multi_ack && ok_to_give_up())
+ packet_write(1, "ACK %s continue\n",
+ sha1_to_hex(sha1));
+ break;
+ default:
memcpy(hex, sha1_to_hex(sha1), 41);
if (multi_ack) {
const char *msg = "ACK %s continue\n";
}
else if (have_obj.nr == 1)
packet_write(1, "ACK %s\n", hex);
+ break;
}
continue;
}
xdemitconf_t const *xecfg) {
long s1, s2, e1, e2, lctx;
xdchange_t *xch, *xche;
- char funcbuf[40];
+ char funcbuf[80];
long funclen = 0;
if (xecfg->flags & XDL_EMIT_COMMON)