summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/config.txt8
-rw-r--r--Documentation/core-tutorial.txt4
-rw-r--r--Documentation/everyday.txt6
-rw-r--r--Documentation/git-branch.txt10
-rw-r--r--Documentation/git-checkout.txt7
-rw-r--r--Documentation/git-read-tree.txt11
-rw-r--r--Documentation/git-rev-parse.txt7
-rw-r--r--Documentation/git-update-ref.txt28
-rw-r--r--Documentation/git-write-tree.txt8
-rw-r--r--Documentation/repository-layout.txt11
-rw-r--r--Documentation/tutorial-2.txt2
-rw-r--r--Documentation/tutorial.txt12
-rw-r--r--builtin-read-tree.c72
-rw-r--r--cache-tree.c26
-rw-r--r--cache-tree.h2
-rw-r--r--cache.h1
-rw-r--r--config.c5
-rwxr-xr-xcontrib/git-svn/git-svn.perl1
-rw-r--r--contrib/git-svn/t/t0001-contrib-git-svn-props.sh3
-rw-r--r--environment.c1
-rw-r--r--fetch-pack.c16
-rw-r--r--fetch.c42
-rw-r--r--fetch.h3
-rwxr-xr-xgit-am.sh2
-rwxr-xr-xgit-applypatch.sh2
-rwxr-xr-xgit-branch.sh14
-rwxr-xr-xgit-checkout.sh19
-rwxr-xr-xgit-clean.sh4
-rwxr-xr-xgit-commit.sh3
-rwxr-xr-xgit-reset.sh2
-rwxr-xr-xgit-send-email.perl34
-rw-r--r--http-fetch.c6
-rw-r--r--local-fetch.c2
-rw-r--r--refs.c374
-rw-r--r--refs.h29
-rw-r--r--sha1_name.c73
-rw-r--r--ssh-fetch.c2
-rwxr-xr-xt/t0000-basic.sh14
-rwxr-xr-xt/t1400-update-ref.sh213
-rwxr-xr-xt/t2101-update-index-reupdate.sh48
-rwxr-xr-xt/t3200-branch.sh31
-rwxr-xr-xt/t3300-funny-names.sh51
-rwxr-xr-xt/t4012-diff-binary.sh17
-rwxr-xr-xt/t5500-fetch-pack.sh30
-rwxr-xr-xt/t6000lib.sh4
-rwxr-xr-xt/t9001-send-email.sh41
-rw-r--r--update-ref.c103
-rw-r--r--write-tree.c23
48 files changed, 1084 insertions, 343 deletions
diff --git a/Documentation/config.txt b/Documentation/config.txt
index d1a4bec0d4..e178ee2de1 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -70,6 +70,14 @@ core.preferSymlinkRefs::
This is sometimes needed to work with old scripts that
expect HEAD to be a symbolic link.
+core.logAllRefUpdates::
+ If true, `git-update-ref` will append a line to
+ "$GIT_DIR/logs/<ref>" listing the new SHA1 and the date/time
+ of the update. If the file does not exist it will be
+ created automatically. This information can be used to
+ determine what commit was the tip of a branch "2 days ago".
+ This value is false by default (no logging).
+
core.repositoryFormatVersion::
Internal variable identifying the repository format and layout
version.
diff --git a/Documentation/core-tutorial.txt b/Documentation/core-tutorial.txt
index d1360ecde2..5a831adf43 100644
--- a/Documentation/core-tutorial.txt
+++ b/Documentation/core-tutorial.txt
@@ -1,5 +1,5 @@
-A short git tutorial
-====================
+A git core tutorial for developers
+==================================
Introduction
------------
diff --git a/Documentation/everyday.txt b/Documentation/everyday.txt
index 4b56370937..2ad2d61300 100644
--- a/Documentation/everyday.txt
+++ b/Documentation/everyday.txt
@@ -66,7 +66,7 @@ $ git prune <4>
<1> running without "--full" is usually cheap and assures the
repository health reasonably well.
<2> check how many loose objects there are and how much
-diskspace is wasted by not repacking.
+disk space is wasted by not repacking.
<3> without "-a" repacks incrementally. repacking every 4-5MB
of loose objects accumulation may be a good rule of thumb.
<4> after repack, prune removes the duplicate loose objects.
@@ -86,7 +86,7 @@ Individual Developer (Standalone)[[Individual Developer (Standalone)]]
----------------------------------------------------------------------
A standalone individual developer does not exchange patches with
-other poeple, and works alone in a single repository, using the
+other people, and works alone in a single repository, using the
following commands.
* gitlink:git-show-branch[1] to see where you are.
@@ -370,7 +370,7 @@ Examples
Run git-daemon to serve /pub/scm from inetd.::
+
------------
-$ grep git /etc/inet.conf
+$ grep git /etc/inetd.conf
git stream tcp nowait nobody \
/usr/bin/git-daemon git-daemon --inetd --syslog --export-all /pub/scm
------------
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index 0b37e2bfc8..d43ef1dec4 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -9,7 +9,7 @@ SYNOPSIS
--------
[verse]
'git-branch' [-r]
-'git-branch' [-f] <branchname> [<start-point>]
+'git-branch' [-l] [-f] <branchname> [<start-point>]
'git-branch' (-d | -D) <branchname>...
DESCRIPTION
@@ -23,7 +23,8 @@ If no <start-point> is given, the branch will be created with a head
equal to that of the currently checked out branch.
With a `-d` or `-D` option, `<branchname>` will be deleted. You may
-specify more than one branch for deletion.
+specify more than one branch for deletion. If the branch currently
+has a ref log then the ref log will also be deleted.
OPTIONS
@@ -34,6 +35,11 @@ OPTIONS
-D::
Delete a branch irrespective of its index status.
+-l::
+ Create the branch's ref log. This activates recording of
+ all changes to made the branch ref, enabling use of date
+ based sha1 expressions such as "<branchname>@{yesterday}".
+
-f::
Force the creation of a new branch even if it means deleting
a branch that already exists with the same name.
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index d82efc00d4..fbdbadc74f 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -8,7 +8,7 @@ git-checkout - Checkout and switch to a branch
SYNOPSIS
--------
[verse]
-'git-checkout' [-f] [-b <new_branch>] [-m] [<branch>]
+'git-checkout' [-f] [-b <new_branch> [-l]] [-m] [<branch>]
'git-checkout' [-m] [<branch>] <paths>...
DESCRIPTION
@@ -40,6 +40,11 @@ OPTIONS
by gitlink:git-check-ref-format[1]. Some of these checks
may restrict the characters allowed in a branch name.
+-l::
+ Create the new branch's ref log. This activates recording of
+ all changes to made the branch ref, enabling use of date
+ based sha1 expressions such as "<branchname>@{yesterday}".
+
-m::
If you have local modifications to one or more files that
are different between the current branch and the branch to
diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt
index 844cfda8d2..1f21d95684 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' (<tree-ish> | [[-m [--aggressive]| --reset] [-u | -i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
+'git-read-tree' (<tree-ish> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
DESCRIPTION
@@ -63,6 +63,15 @@ OPTIONS
* when both sides adds a path identically. The resolution
is to add that path.
+--prefix=<prefix>/::
+ Keep the current index contents, and read the contents
+ of named tree-ish under directory at `<prefix>`. The
+ original index file cannot have anything at the path
+ `<prefix>` itself, and have nothing in `<prefix>/`
+ directory. Note that the `<prefix>/` value must end
+ with a slash.
+
+
<tree-ish#>::
The id of the tree object(s) to be read/merged.
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index ab896fcf83..b894694367 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -124,6 +124,13 @@ syntax.
happen to have both heads/master and tags/master, you can
explicitly say 'heads/master' to tell git which one you mean.
+* A suffix '@' followed by a date specification enclosed in a brace
+ pair (e.g. '\{yesterday\}', '\{1 month 2 weeks 3 days 1 hour 1
+ second ago\}' or '\{1979-02-26 18:30:00\}') to specify the value
+ of the ref at a prior point in time. This suffix may only be
+ used immediately following a ref name and the ref must have an
+ existing log ($GIT_DIR/logs/<ref>).
+
* A suffix '{caret}' to a revision parameter means the first parent of
that commit object. '{caret}<n>' means the <n>th parent (i.e.
'rev{caret}'
diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt
index 475237f19e..e062030e91 100644
--- a/Documentation/git-update-ref.txt
+++ b/Documentation/git-update-ref.txt
@@ -7,7 +7,7 @@ git-update-ref - update the object name stored in a ref safely
SYNOPSIS
--------
-'git-update-ref' <ref> <newvalue> [<oldvalue>]
+'git-update-ref' [-m <reason>] <ref> <newvalue> [<oldvalue>]
DESCRIPTION
-----------
@@ -49,6 +49,32 @@ for reading but not for writing (so we'll never write through a
ref symlink to some other tree, if you have copied a whole
archive by creating a symlink tree).
+Logging Updates
+---------------
+If config parameter "core.logAllRefUpdates" is true or the file
+"$GIT_DIR/logs/<ref>" exists then `git-update-ref` will append
+a line to the log file "$GIT_DIR/logs/<ref>" (dereferencing all
+symbolic refs before creating the log name) describing the change
+in ref value. Log lines are formatted as:
+
+ . oldsha1 SP newsha1 SP committer LF
++
+Where "oldsha1" is the 40 character hexadecimal value previously
+stored in <ref>, "newsha1" is the 40 character hexadecimal value of
+<newvalue> and "committer" is the committer's name, email address
+and date in the standard GIT committer ident format.
+
+Optionally with -m:
+
+ . oldsha1 SP newsha1 SP committer TAB message LF
++
+Where all fields are as described above and "message" is the
+value supplied to the -m option.
+
+An update will fail (without changing <ref>) if the current user is
+unable to create a new log file, append to the existing log file
+or does not have committer information available.
+
Author
------
Written by Linus Torvalds <torvalds@osdl.org>.
diff --git a/Documentation/git-write-tree.txt b/Documentation/git-write-tree.txt
index 77e12cb949..c85fa89c30 100644
--- a/Documentation/git-write-tree.txt
+++ b/Documentation/git-write-tree.txt
@@ -8,7 +8,7 @@ git-write-tree - Creates a tree object from the current index
SYNOPSIS
--------
-'git-write-tree' [--missing-ok]
+'git-write-tree' [--missing-ok] [--prefix=<prefix>/]
DESCRIPTION
-----------
@@ -30,6 +30,12 @@ OPTIONS
directory exist in the object database. This option disables this
check.
+--prefix=<prefix>/::
+ Writes a tree object that represents a subdirectory
+ `<prefix>`. This can be used to write the tree object
+ for a subproject that is in the named subdirectory.
+
+
Author
------
Written by Linus Torvalds <torvalds@osdl.org>
diff --git a/Documentation/repository-layout.txt b/Documentation/repository-layout.txt
index 98fbe7db52..b52dfdc308 100644
--- a/Documentation/repository-layout.txt
+++ b/Documentation/repository-layout.txt
@@ -128,3 +128,14 @@ remotes::
Stores shorthands to be used to give URL and default
refnames to interact with remote repository to `git
fetch`, `git pull` and `git push` commands.
+
+logs::
+ Records of changes made to refs are stored in this
+ directory. See the documentation on git-update-ref
+ for more information.
+
+logs/refs/heads/`name`::
+ Records all changes made to the branch tip named `name`.
+
+logs/refs/tags/`name`::
+ Records all changes made to the tag named `name`.
diff --git a/Documentation/tutorial-2.txt b/Documentation/tutorial-2.txt
index 08d3453e5c..9c9500c1f1 100644
--- a/Documentation/tutorial-2.txt
+++ b/Documentation/tutorial-2.txt
@@ -377,7 +377,7 @@ At this point you should know everything necessary to read the man
pages for any of the git commands; one good place to start would be
with the commands mentioned in link:everyday.html[Everyday git]. You
should be able to find any unknown jargon in the
-link:glossary.html[Glosssay].
+link:glossary.html[Glossary].
The link:cvs-migration.html[CVS migration] document explains how to
import a CVS repository into git, and shows how to use git in a
diff --git a/Documentation/tutorial.txt b/Documentation/tutorial.txt
index 79781adf4f..039a8598e3 100644
--- a/Documentation/tutorial.txt
+++ b/Documentation/tutorial.txt
@@ -429,16 +429,24 @@ $ gitk --since="2 weeks ago" drivers/
-------------------------------------
allows you to browse any commits from the last 2 weeks of commits
-that modified files under the "drivers" directory.
+that modified files under the "drivers" directory. (Note: you can
+adjust gitk's fonts by holding down the control key while pressing
+"-" or "+".)
Finally, most commands that take filenames will optionally allow you
to precede any filename by a commit, to specify a particular version
-fo the file:
+of the file:
-------------------------------------
$ git diff v2.5:Makefile HEAD:Makefile.in
-------------------------------------
+You can also use "git cat-file -p" to see any such file:
+
+-------------------------------------
+$ git cat-file -p v2.5:Makefile
+-------------------------------------
+
Next Steps
----------
diff --git a/builtin-read-tree.c b/builtin-read-tree.c
index 10afd46968..0c6ba3d8a5 100644
--- a/builtin-read-tree.c
+++ b/builtin-read-tree.c
@@ -24,6 +24,7 @@ static int trivial_merges_only = 0;
static int aggressive = 0;
static int verbose_update = 0;
static volatile int progress_update = 0;
+static const char *prefix = NULL;
static int head_idx = -1;
static int merge_size = 0;
@@ -411,7 +412,8 @@ static int unpack_trees(merge_fn_t fn)
posns[i] = create_tree_entry_list((struct tree *) posn->item);
posn = posn->next;
}
- if (unpack_trees_rec(posns, len, "", fn, &indpos))
+ if (unpack_trees_rec(posns, len, prefix ? prefix : "",
+ fn, &indpos))
return -1;
}
@@ -761,6 +763,28 @@ static int twoway_merge(struct cache_entry **src)
}
/*
+ * Bind merge.
+ *
+ * Keep the index entries at stage0, collapse stage1 but make sure
+ * stage0 does not have anything there.
+ */
+static int bind_merge(struct cache_entry **src)
+{
+ struct cache_entry *old = src[0];
+ struct cache_entry *a = src[1];
+
+ if (merge_size != 1)
+ return error("Cannot do a bind merge of %d trees\n",
+ merge_size);
+ if (a && old)
+ die("Entry '%s' overlaps. Cannot bind.", a->name);
+ if (!a)
+ return keep_entry(old);
+ else
+ return merged_entry(a, NULL);
+}
+
+/*
* One-way merge.
*
* The rule is:
@@ -775,8 +799,10 @@ static int oneway_merge(struct cache_entry **src)
return error("Cannot do a oneway merge of %d trees",
merge_size);
- if (!a)
+ if (!a) {
+ invalidate_ce_path(old);
return deleted_entry(old, old);
+ }
if (old && same(old, a)) {
if (reset) {
struct stat st;
@@ -849,7 +875,7 @@ static void prime_cache_tree(void)
}
-static const char read_tree_usage[] = "git-read-tree (<sha> | -m [--aggressive] [-u | -i] <sha1> [<sha2> [<sha3>]])";
+static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] <sha1> [<sha2> [<sha3>]])";
static struct cache_file cache_file;
@@ -894,9 +920,24 @@ int cmd_read_tree(int argc, const char **argv, char **envp)
continue;
}
+ /* "--prefix=<subdirectory>/" means keep the current index
+ * entries and put the entries from the tree under the
+ * given subdirectory.
+ */
+ if (!strncmp(arg, "--prefix=", 9)) {
+ if (stage || merge || prefix)
+ usage(read_tree_usage);
+ prefix = arg + 9;
+ merge = 1;
+ stage = 1;
+ if (read_cache_unmerged())
+ die("you need to resolve your current index first");
+ continue;
+ }
+
/* This differs from "-m" in that we'll silently ignore unmerged entries */
if (!strcmp(arg, "--reset")) {
- if (stage || merge)
+ if (stage || merge || prefix)
usage(read_tree_usage);
reset = 1;
merge = 1;
@@ -917,7 +958,7 @@ int cmd_read_tree(int argc, const char **argv, char **envp)
/* "-m" stands for "merge", meaning we start in stage 1 */
if (!strcmp(arg, "-m")) {
- if (stage || merge)
+ if (stage || merge || prefix)
usage(read_tree_usage);
if (read_cache_unmerged())
die("you need to resolve your current index first");
@@ -939,12 +980,31 @@ int cmd_read_tree(int argc, const char **argv, char **envp)
if ((update||index_only) && !merge)
usage(read_tree_usage);
+ if (prefix) {
+ int pfxlen = strlen(prefix);
+ int pos;
+ if (prefix[pfxlen-1] != '/')
+ die("prefix must end with /");
+ if (stage != 2)
+ die("binding merge takes only one tree");
+ pos = cache_name_pos(prefix, pfxlen);
+ if (0 <= pos)
+ die("corrupt index file");
+ pos = -pos-1;
+ if (pos < active_nr &&
+ !strncmp(active_cache[pos]->name, prefix, pfxlen))
+ die("subdirectory '%s' already exists.", prefix);
+ pos = cache_name_pos(prefix, pfxlen-1);
+ if (0 <= pos)
+ die("file '%.*s' already exists.", pfxlen-1, prefix);
+ }
+
if (merge) {
if (stage < 2)
die("just how do you expect me to merge %d trees?", stage-1);
switch (stage - 1) {
case 1:
- fn = oneway_merge;
+ fn = prefix ? bind_merge : oneway_merge;
break;
case 2:
fn = twoway_merge;
diff --git a/cache-tree.c b/cache-tree.c
index a880c97b38..d9f7e1e3dd 100644
--- a/cache-tree.c
+++ b/cache-tree.c
@@ -529,3 +529,29 @@ struct cache_tree *cache_tree_read(const char *buffer, unsigned long size)
return NULL; /* not the whole tree */
return read_one(&buffer, &size);
}
+
+struct cache_tree *cache_tree_find(struct cache_tree *it, const char *path)
+{
+ while (*path) {
+ const char *slash;
+ struct cache_tree_sub *sub;
+
+ slash = strchr(path, '/');
+ if (!slash)
+ slash = path + strlen(path);
+ /* between path and slash is the name of the
+ * subtree to look for.
+ */
+ sub = find_subtree(it, path, slash - path, 0);
+ if (!sub)
+ return NULL;
+ it = sub->cache_tree;
+ if (slash)
+ while (*slash && *slash == '/')
+ slash++;
+ if (!slash || !*slash)
+ return it; /* prefix ended with slashes */
+ path = slash;
+ }
+ return it;
+}
diff --git a/cache-tree.h b/cache-tree.h
index 72c64801f5..119407e3a1 100644
--- a/cache-tree.h
+++ b/cache-tree.h
@@ -28,4 +28,6 @@ struct cache_tree *cache_tree_read(const char *buffer, unsigned long size);
int cache_tree_fully_valid(struct cache_tree *);
int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int);
+struct cache_tree *cache_tree_find(struct cache_tree *, const char *);
+
#endif
diff --git a/cache.h b/cache.h
index f11d5e7dcf..d530af97cc 100644
--- a/cache.h
+++ b/cache.h
@@ -179,6 +179,7 @@ extern void rollback_index_file(struct cache_file *);
extern int trust_executable_bit;
extern int assume_unchanged;
extern int prefer_symlink_refs;
+extern int log_all_ref_updates;
extern int warn_ambiguous_refs;
extern int diff_rename_limit_default;
extern int shared_repository;
diff --git a/config.c b/config.c
index 0248c6d8a5..2ae6153e5e 100644
--- a/config.c
+++ b/config.c
@@ -269,6 +269,11 @@ int git_default_config(const char *var, const char *value)
return 0;
}
+ if (!strcmp(var, "core.logallrefupdates")) {
+ log_all_ref_updates = git_config_bool(var, value);
+ return 0;
+ }
+
if (!strcmp(var, "core.warnambiguousrefs")) {
warn_ambiguous_refs = git_config_bool(var, value);
return 0;
diff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl
index b3e0684c44..aac877974d 100755
--- a/contrib/git-svn/git-svn.perl
+++ b/contrib/git-svn/git-svn.perl
@@ -567,7 +567,6 @@ sub precommit_check {
sub svn_checkout_tree {
my ($svn_rev, $treeish) = @_;
my $from = file_to_s("$REV_DIR/$svn_rev");
- assert_svn_wc_clean($svn_rev);
assert_tree($from);
print "diff-tree $from $treeish\n";
my $pid = open my $diff_fh, '-|';
diff --git a/contrib/git-svn/t/t0001-contrib-git-svn-props.sh b/contrib/git-svn/t/t0001-contrib-git-svn-props.sh
index 6fa7889e9a..23a5a2a223 100644
--- a/contrib/git-svn/t/t0001-contrib-git-svn-props.sh
+++ b/contrib/git-svn/t/t0001-contrib-git-svn-props.sh
@@ -20,9 +20,10 @@ a_empty_cr=
a_empty_crlf=
cd import
- cat >> kw.c <<''
+ cat >> kw.c <<\EOF
/* Make it look like somebody copied a file from CVS into SVN: */
/* $Id: kw.c,v 1.1.1.1 1994/03/06 00:00:00 eric Exp $ */
+EOF
printf "Hello\r\nWorld\r\n" > crlf
a_crlf=`git-hash-object -w crlf`
diff --git a/environment.c b/environment.c
index 444c99ed6e..2e79eab18d 100644
--- a/environment.c
+++ b/environment.c
@@ -14,6 +14,7 @@ char git_default_name[MAX_GITNAME];
int trust_executable_bit = 1;
int assume_unchanged = 0;
int prefer_symlink_refs = 0;
+int log_all_ref_updates = 0;
int warn_ambiguous_refs = 1;
int repository_format_version = 0;
char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8";
diff --git a/fetch-pack.c b/fetch-pack.c
index 8daa93d024..8371348556 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -18,6 +18,12 @@ static const char *exec = "git-upload-pack";
#define SEEN (1U << 3)
#define POPPED (1U << 4)
+/*
+ * After sending this many "have"s if we do not get any new ACK , we
+ * give up traversing our history.
+ */
+#define MAX_IN_VAIN 256
+
static struct commit_list *rev_list = NULL;
static int non_common_revs = 0, multi_ack = 0, use_thin_pack = 0;
@@ -134,6 +140,8 @@ static int find_common(int fd[2], unsigned char *result_sha1,
int fetching;
int count = 0, flushes = 0, retval;
const unsigned char *sha1;
+ unsigned in_vain = 0;
+ int got_continue = 0;
for_each_ref(rev_list_insert_ref);
@@ -172,6 +180,7 @@ static int find_common(int fd[2], unsigned char *result_sha1,
packet_write(fd[1], "have %s\n", sha1_to_hex(sha1));
if (verbose)
fprintf(stderr, "have %s\n", sha1_to_hex(sha1));
+ in_vain++;
if (!(31 & ++count)) {
int ack;
@@ -200,9 +209,16 @@ static int find_common(int fd[2], unsigned char *result_sha1,
lookup_commit(result_sha1);
mark_common(commit, 0, 1);
retval = 0;
+ in_vain = 0;
+ got_continue = 1;
}
} while (ack);
flushes--;
+ if (got_continue && MAX_IN_VAIN < in_vain) {
+ if (verbose)
+ fprintf(stderr, "giving up\n");
+ break; /* give up */
+ }
}
}
done:
diff --git a/fetch.c b/fetch.c
index ec2d8c3d9b..e9347bafeb 100644
--- a/fetch.c
+++ b/fetch.c
@@ -9,6 +9,7 @@
#include "refs.h"
const char *write_ref = NULL;
+const char *write_ref_log_details = NULL;
int get_tree = 0;
int get_history = 0;
@@ -208,23 +209,48 @@ static int mark_complete(const char *path, const unsigned char *sha1)
int pull(char *target)
{
+ struct ref_lock *lock;
unsigned char sha1[20];
+ char *msg;
+ int ret;
save_commit_buffer = 0;
track_object_refs = 0;
+ if (write_ref) {
+ lock = lock_ref_sha1(write_ref, NULL, 0);
+ if (!lock) {
+ error("Can't lock ref %s", write_ref);
+ return -1;
+ }
+ }
if (!get_recover)
for_each_ref(mark_complete);
- if (interpret_target(target, sha1))
- return error("Could not interpret %s as something to pull",
- target);
- if (process(lookup_unknown_object(sha1)))
+ if (interpret_target(target, sha1)) {
+ error("Could not interpret %s as something to pull", target);
+ unlock_ref(lock);
return -1;
- if (loop())
+ }
+ if (process(lookup_unknown_object(sha1))) {
+ unlock_ref(lock);
return -1;
-
- if (write_ref)
- write_ref_sha1_unlocked(write_ref, sha1);
+ }
+ if (loop()) {
+ unlock_ref(lock);
+ return -1;
+ }
+
+ if (write_ref) {
+ if (write_ref_log_details) {
+ msg = xmalloc(strlen(write_ref_log_details) + 12);
+ sprintf(msg, "fetch from %s", write_ref_log_details);
+ } else
+ msg = NULL;
+ ret = write_ref_sha1(lock, sha1, msg ? msg : "fetch (unknown)");
+ if (msg)
+ free(msg);
+ return ret;
+ }
return 0;
}
diff --git a/fetch.h b/fetch.h
index 001a6b8e2d..841bb1af9c 100644
--- a/fetch.h
+++ b/fetch.h
@@ -25,6 +25,9 @@ extern int fetch_ref(char *ref, unsigned char *sha1);
/* If set, the ref filename to write the target value to. */
extern const char *write_ref;
+/* If set additional text will appear in the ref log. */
+extern const char *write_ref_log_details;
+
/* Set to fetch the target tree. */
extern int get_tree;
diff --git a/git-am.sh b/git-am.sh
index 97ec2d0c7d..4232e27411 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -413,7 +413,7 @@ do
parent=$(git-rev-parse --verify HEAD) &&
commit=$(git-commit-tree $tree -p $parent <"$dotest/final-commit") &&
echo Committed: $commit &&
- git-update-ref HEAD $commit $parent ||
+ git-update-ref -m "am: $SUBJECT" HEAD $commit $parent ||
stop_here $this
if test -x "$GIT_DIR"/hooks/post-applypatch
diff --git a/git-applypatch.sh b/git-applypatch.sh
index 12cab1e0d4..e4b09472e1 100755
--- a/git-applypatch.sh
+++ b/git-applypatch.sh
@@ -204,7 +204,7 @@ echo Wrote tree $tree
parent=$(git-rev-parse --verify HEAD) &&
commit=$(git-commit-tree $tree -p $parent <"$final") || exit 1
echo Committed: $commit
-git-update-ref HEAD $commit $parent || exit
+git-update-ref -m "applypatch: $SUBJECT" HEAD $commit $parent || exit
if test -x "$GIT_DIR"/hooks/post-applypatch
then
diff --git a/git-branch.sh b/git-branch.sh
index 134e68cf7f..e0501ec23f 100755
--- a/git-branch.sh
+++ b/git-branch.sh
@@ -1,6 +1,6 @@
#!/bin/sh
-USAGE='[(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]] | -r'
+USAGE='[-l] [(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]] | -r'
LONG_USAGE='If no arguments, show available branches and mark current branch with a star.
If one argument, create a new branch <branchname> based off of current HEAD.
If two arguments, create a new branch <branchname> based off of <start-point>.'
@@ -42,6 +42,7 @@ If you are sure you want to delete it, run 'git branch -D $branch_name'."
esac
;;
esac
+ rm -f "$GIT_DIR/logs/refs/heads/$branch_name"
rm -f "$GIT_DIR/refs/heads/$branch_name"
echo "Deleted branch $branch_name."
done
@@ -55,6 +56,7 @@ ls_remote_branches () {
}
force=
+create_log=
while case "$#,$1" in 0,*) break ;; *,-*) ;; *) break ;; esac
do
case "$1" in
@@ -69,6 +71,9 @@ do
-f)
force="$1"
;;
+ -l)
+ create_log="yes"
+ ;;
--)
shift
break
@@ -117,4 +122,9 @@ then
die "cannot force-update the current branch."
fi
fi
-git update-ref "refs/heads/$branchname" $rev
+if test "$create_log" = 'yes'
+then
+ mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$branchname")
+ touch "$GIT_DIR/logs/refs/heads/$branchname"
+fi
+git update-ref -m "branch: Created from $head" "refs/heads/$branchname" $rev
diff --git a/git-checkout.sh b/git-checkout.sh
index a11c939c30..564117f006 100755
--- a/git-checkout.sh
+++ b/git-checkout.sh
@@ -5,10 +5,13 @@ SUBDIRECTORY_OK=Sometimes
. git-sh-setup
old=$(git-rev-parse HEAD)
+old_name=HEAD
new=
+new_name=
force=
branch=
newbranch=
+newbranch_log=
merge=
while [ "$#" != "0" ]; do
arg="$1"
@@ -24,6 +27,9 @@ while [ "$#" != "0" ]; do
git-check-ref-format "heads/$newbranch" ||
die "git checkout: we do not like '$newbranch' as a branch name."
;;
+ "-l")
+ newbranch_log=1
+ ;;
"-f")
force=1
;;
@@ -44,6 +50,7 @@ while [ "$#" != "0" ]; do
exit 1
fi
new="$rev"
+ new_name="$arg^0"
if [ -f "$GIT_DIR/refs/heads/$arg" ]; then
branch="$arg"
fi
@@ -51,9 +58,11 @@ while [ "$#" != "0" ]; do
then
# checking out selected paths from a tree-ish.
new="$rev"
+ new_name="$arg^{tree}"
branch=
else
new=
+ new_name=
branch=
set x "$arg" "$@"
shift
@@ -114,7 +123,7 @@ then
cd "$cdup"
fi
-[ -z "$new" ] && new=$old
+[ -z "$new" ] && new=$old && new_name="$old_name"
# If we don't have an old branch that we're switching to,
# and we don't have a new branch name for the target we
@@ -187,9 +196,11 @@ fi
#
if [ "$?" -eq 0 ]; then
if [ "$newbranch" ]; then
- leading=`expr "refs/heads/$newbranch" : '\(.*\)/'` &&
- mkdir -p "$GIT_DIR/$leading" &&
- echo $new >"$GIT_DIR/refs/heads/$newbranch" || exit
+ if [ "$newbranch_log" ]; then
+ mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$newbranch")
+ touch "$GIT_DIR/logs/refs/heads/$newbranch"
+ fi
+ git-update-ref -m "checkout: Created from $new_name" "refs/heads/$newbranch" $new || exit
branch="$newbranch"
fi
[ "$branch" ] &&
diff --git a/git-clean.sh b/git-clean.sh
index bb56264e04..3834323bcf 100755
--- a/git-clean.sh
+++ b/git-clean.sh
@@ -19,8 +19,8 @@ ignored=
ignoredonly=
cleandir=
quiet=
-rmf="rm -f"
-rmrf="rm -rf"
+rmf="rm -f --"
+rmrf="rm -rf --"
rm_refuse="echo Not removing"
echo1="echo"
diff --git a/git-commit.sh b/git-commit.sh
index 1983d45828..91f28f9a23 100755
--- a/git-commit.sh
+++ b/git-commit.sh
@@ -690,7 +690,8 @@ then
rm -f "$TMP_INDEX"
fi &&
commit=$(cat "$GIT_DIR"/COMMIT_MSG | git-commit-tree $tree $PARENTS) &&
- git-update-ref HEAD $commit $current &&
+ rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) &&
+ git-update-ref -m "commit: $rlogm" HEAD $commit $current &&
rm -f -- "$GIT_DIR/MERGE_HEAD" &&
if test -f "$NEXT_INDEX"
then
diff --git a/git-reset.sh b/git-reset.sh
index 0ee3e3e154..296f3b779b 100755
--- a/git-reset.sh
+++ b/git-reset.sh
@@ -48,7 +48,7 @@ then
else
rm -f "$GIT_DIR/ORIG_HEAD"
fi
-git-update-ref HEAD "$rev"
+git-update-ref -m "reset $reset_type $@" HEAD "$rev"
case "$reset_type" in
--hard )
diff --git a/git-send-email.perl b/git-send-email.perl
index 312a4ea2aa..0e368fff0c 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -37,7 +37,8 @@ sub cleanup_compose_files();
my $compose_filename = ".msg.$$";
# Variables we fill in automatically, or via prompting:
-my (@to,@cc,@initial_cc,$initial_reply_to,$initial_subject,@files,$from,$compose,$time);
+my (@to,@cc,@initial_cc,@bcclist,
+ $initial_reply_to,$initial_subject,@files,$from,$compose,$time);
# Behavior modification variables
my ($chain_reply_to, $quiet, $suppress_from, $no_signed_off_cc) = (1, 0, 0, 0);
@@ -56,6 +57,7 @@ my $rc = GetOptions("from=s" => \$from,
"subject=s" => \$initial_subject,
"to=s" => \@to,
"cc=s" => \@initial_cc,
+ "bcc=s" => \@bcclist,
"chain-reply-to!" => \$chain_reply_to,
"smtp-server=s" => \$smtp_server,
"compose" => \$compose,
@@ -160,6 +162,7 @@ sub expand_aliases {
@to = expand_aliases(@to);
@initial_cc = expand_aliases(@initial_cc);
+@bcclist = expand_aliases(@bcclist);
if (!defined $initial_subject && $compose) {
do {
@@ -269,6 +272,9 @@ Options:
--cc Specify an initial "Cc:" list for the entire series
of emails.
+ --bcc Specify a list of email addresses that should be Bcc:
+ on all the emails.
+
--compose Use \$EDITOR to edit an introductory message for the
patch series.
@@ -303,7 +309,7 @@ EOT
}
# Variables we set as part of the loop over files
-our ($message_id, $cc, %mail, $subject, $reply_to, $message);
+our ($message_id, $cc, %mail, $subject, $reply_to, $references, $message);
sub extract_valid_address {
my $address = shift;
@@ -316,7 +322,11 @@ sub extract_valid_address {
} else {
# less robust/correct than the monster regexp in Email::Valid,
# but still does a 99% job, and one less dependency
- return ($address =~ /([^\"<>\s]+@[^<>\s]+)/);
+ my $cleaned_address;
+ if ($address =~ /([^\"<>\s]+@[^<>\s]+)/) {
+ $cleaned_address = $1;
+ }
+ return $cleaned_address;
}
}
@@ -348,7 +358,7 @@ sub send_message
{
my @recipients = unique_email_list(@to);
my $to = join (",\n\t", @recipients);
- @recipients = unique_email_list(@recipients,@cc);
+ @recipients = unique_email_list(@recipients,@cc,@bcclist);
my $date = strftime('%a, %d %b %Y %H:%M:%S %z', localtime($time++));
my $gitversion = '@@GIT_VERSION@@';
if ($gitversion =~ m/..GIT_VERSION../) {
@@ -367,13 +377,19 @@ Date: $date
Message-Id: $message_id
X-Mailer: git-send-email $gitversion
";
- $header .= "In-Reply-To: $reply_to\n" if $reply_to;
+ if ($reply_to) {
+
+ $header .= "In-Reply-To: $reply_to\n";
+ $header .= "References: $references\n";
+ }
if ($smtp_server =~ m#^/#) {
my $pid = open my $sm, '|-';
defined $pid or die $!;
if (!$pid) {
- exec($smtp_server,'-i',@recipients) or die $!;
+ exec($smtp_server,'-i',
+ map { scalar extract_valid_address($_) }
+ @recipients) or die $!;
}
print $sm "$header\n$message";
close $sm or die $?;
@@ -406,6 +422,7 @@ X-Mailer: git-send-email $gitversion
}
$reply_to = $initial_reply_to;
+$references = $initial_reply_to || '';
make_message_id();
$subject = $initial_subject;
@@ -482,6 +499,11 @@ foreach my $t (@files) {
# set up for the next message
if ($chain_reply_to || length($reply_to) == 0) {
$reply_to = $message_id;
+ if (length $references > 0) {
+ $references .= " $message_id";
+ } else {
+ $references = "$message_id";
+ }
}
make_message_id();
}
diff --git a/http-fetch.c b/http-fetch.c
index 861644b27e..661c909152 100644
--- a/http-fetch.c
+++ b/http-fetch.c
@@ -1223,6 +1223,7 @@ int main(int argc, char **argv)
int rc = 0;
setup_git_directory();
+ git_config(git_default_config);
while (arg < argc && argv[arg][0] == '-') {
if (argv[arg][1] == 't') {
@@ -1249,6 +1250,7 @@ int main(int argc, char **argv)
}
commit_id = argv[arg];
url = argv[arg + 1];
+ write_ref_log_details = url;
http_init();
@@ -1269,10 +1271,10 @@ int main(int argc, char **argv)
if (pull(commit_id))
rc = 1;
- curl_slist_free_all(no_pragma_header);
-
http_cleanup();
+ curl_slist_free_all(no_pragma_header);
+
if (corrupt_object_found) {
fprintf(stderr,
"Some loose object were found to be corrupt, but they might be just\n"
diff --git a/local-fetch.c b/local-fetch.c
index fa9e697fd3..ffa4887570 100644
--- a/local-fetch.c
+++ b/local-fetch.c
@@ -208,6 +208,7 @@ int main(int argc, char **argv)
int arg = 1;
setup_git_directory();
+ git_config(git_default_config);
while (arg < argc && argv[arg][0] == '-') {
if (argv[arg][1] == 't')
@@ -239,6 +240,7 @@ int main(int argc, char **argv)
usage(local_pull_usage);
commit_id = argv[arg];
path = argv[arg + 1];
+ write_ref_log_details = path;
if (pull(commit_id))
return 1;
diff --git a/refs.c b/refs.c
index 0f3491f871..eeb1196ec4 100644
--- a/refs.c
+++ b/refs.c
@@ -142,6 +142,8 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u
namelen = strlen(de->d_name);
if (namelen > 255)
continue;
+ if (namelen>5 && !strcmp(de->d_name+namelen-5,".lock"))
+ continue;
memcpy(path + baselen, de->d_name, namelen+1);
if (stat(git_path("%s", path), &st) < 0)
continue;
@@ -198,26 +200,6 @@ int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *sha1))
return do_for_each_ref("refs/remotes", fn, 13);
}
-static char *ref_file_name(const char *ref)
-{
- char *base = get_refs_directory();
- int baselen = strlen(base);
- int reflen = strlen(ref);
- char *ret = xmalloc(baselen + 2 + reflen);
- sprintf(ret, "%s/%s", base, ref);
- return ret;
-}
-
-static char *ref_lock_file_name(const char *ref)
-{
- char *base = get_refs_directory();
- int baselen = strlen(base);
- int reflen = strlen(ref);
- char *ret = xmalloc(baselen + 7 + reflen);
- sprintf(ret, "%s/%s.lock", base, ref);
- return ret;
-}
-
int get_ref_sha1(const char *ref, unsigned char *sha1)
{
if (check_ref_format(ref))
@@ -225,94 +207,6 @@ int get_ref_sha1(const char *ref, unsigned char *sha1)
return read_ref(git_path("refs/%s", ref), sha1);
}
-static int lock_ref_file(const char *filename, const char *lock_filename,
- const unsigned char *old_sha1)
-{
- int fd = open(lock_filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
- unsigned char current_sha1[20];
- int retval;
- if (fd < 0) {
- return error("Couldn't open lock file for %s: %s",
- filename, strerror(errno));
- }
- retval = read_ref(filename, current_sha1);
- if (old_sha1) {
- if (retval) {
- close(fd);
- unlink(lock_filename);
- return error("Could not read the current value of %s",
- filename);
- }
- if (memcmp(current_sha1, old_sha1, 20)) {
- close(fd);
- unlink(lock_filename);
- error("The current value of %s is %s",
- filename, sha1_to_hex(current_sha1));
- return error("Expected %s",
- sha1_to_hex(old_sha1));
- }
- } else {
- if (!retval) {
- close(fd);
- unlink(lock_filename);
- return error("Unexpectedly found a value of %s for %s",
- sha1_to_hex(current_sha1), filename);
- }
- }
- return fd;
-}
-
-int lock_ref_sha1(const char *ref, const unsigned char *old_sha1)
-{
- char *filename;
- char *lock_filename;
- int retval;
- if (check_ref_format(ref))
- return -1;
- filename = ref_file_name(ref);
- lock_filename = ref_lock_file_name(ref);
- retval = lock_ref_file(filename, lock_filename, old_sha1);
- free(filename);
- free(lock_filename);
- return retval;
-}
-
-static int write_ref_file(const char *filename,
- const char *lock_filename, int fd,
- const unsigned char *sha1)
-{
- char *hex = sha1_to_hex(sha1);
- char term = '\n';
- if (write(fd, hex, 40) < 40 ||
- write(fd, &term, 1) < 1) {
- error("Couldn't write %s", filename);
- close(fd);
- return -1;
- }
- close(fd);
- rename(lock_filename, filename);
- return 0;
-}
-
-int write_ref_sha1(const char *ref, int fd, const unsigned char *sha1)
-{
- char *filename;
- char *lock_filename;
- int retval;
- if (fd < 0)
- return -1;
- if (check_ref_format(ref))
- return -1;
- filename = ref_file_name(ref);
- lock_filename = ref_lock_file_name(ref);
- if (safe_create_leading_directories(filename))
- die("unable to create leading directory for %s", filename);
- retval = write_ref_file(filename, lock_filename, fd, sha1);
- free(filename);
- free(lock_filename);
- return retval;
-}
-
/*
* Make sure "ref" is something reasonable to have under ".git/refs/";
* We do not like it if:
@@ -365,25 +259,255 @@ int check_ref_format(const char *ref)
}
}
-int write_ref_sha1_unlocked(const char *ref, const unsigned char *sha1)
+static struct ref_lock* verify_lock(struct ref_lock *lock,
+ const unsigned char *old_sha1, int mustexist)
+{
+ char buf[40];
+ int nr, fd = open(lock->ref_file, O_RDONLY);
+ if (fd < 0 && (mustexist || errno != ENOENT)) {
+ error("Can't verify ref %s", lock->ref_file);
+ unlock_ref(lock);
+ return NULL;
+ }
+ nr = read(fd, buf, 40);
+ close(fd);
+ if (nr != 40 || get_sha1_hex(buf, lock->old_sha1) < 0) {
+ error("Can't verify ref %s", lock->ref_file);
+ unlock_ref(lock);
+ return NULL;
+ }
+ if (memcmp(lock->old_sha1, old_sha1, 20)) {
+ error("Ref %s is at %s but expected %s", lock->ref_file,
+ sha1_to_hex(lock->old_sha1), sha1_to_hex(old_sha1));
+ unlock_ref(lock);
+ return NULL;
+ }
+ return lock;
+}
+
+static struct ref_lock* lock_ref_sha1_basic(const char *path,
+ int plen,
+ const unsigned char *old_sha1, int mustexist)
+{
+ struct ref_lock *lock;
+ struct stat st;
+
+ lock = xcalloc(1, sizeof(struct ref_lock));
+ lock->lock_fd = -1;
+
+ plen = strlen(path) - plen;
+ path = resolve_ref(path, lock->old_sha1, mustexist);
+ if (!path) {
+ unlock_ref(lock);
+ return NULL;
+ }
+
+ lock->ref_file = strdup(path);
+ lock->lock_file = strdup(mkpath("%s.lock", lock->ref_file));
+ lock->log_file = strdup(git_path("logs/%s", lock->ref_file + plen));
+ lock->force_write = lstat(lock->ref_file, &st) && errno == ENOENT;
+
+ if (safe_create_leading_directories(lock->lock_file))
+ die("unable to create directory for %s", lock->lock_file);
+ lock->lock_fd = open(lock->lock_file,
+ O_WRONLY | O_CREAT | O_EXCL, 0666);
+ if (lock->lock_fd < 0) {
+ error("Couldn't open lock file %s: %s",
+ lock->lock_file, strerror(errno));
+ unlock_ref(lock);
+ return NULL;
+ }
+
+ return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock;
+}
+
+struct ref_lock* lock_ref_sha1(const char *ref,
+ const unsigned char *old_sha1, int mustexist)
{
- char *filename;
- char *lock_filename;
- int fd;
- int retval;
if (check_ref_format(ref))
+ return NULL;
+ return lock_ref_sha1_basic(git_path("refs/%s", ref),
+ 5 + strlen(ref), old_sha1, mustexist);
+}
+
+struct ref_lock* lock_any_ref_for_update(const char *ref,
+ const unsigned char *old_sha1, int mustexist)
+{
+ return lock_ref_sha1_basic(git_path("%s", ref),
+ strlen(ref), old_sha1, mustexist);
+}
+
+void unlock_ref (struct ref_lock *lock)
+{
+ if (lock->lock_fd >= 0) {
+ close(lock->lock_fd);
+ unlink(lock->lock_file);
+ }
+ if (lock->ref_file)
+ free(lock->ref_file);
+ if (lock->lock_file)
+ free(lock->lock_file);
+ if (lock->log_file)
+ free(lock->log_file);
+ free(lock);
+}
+
+static int log_ref_write(struct ref_lock *lock,
+ const unsigned char *sha1, const char *msg)
+{
+ int logfd, written, oflags = O_APPEND | O_WRONLY;
+ unsigned maxlen, len;
+ char *logrec;
+ const char *comitter;
+
+ if (log_all_ref_updates) {
+ if (safe_create_leading_directories(lock->log_file) < 0)
+ return error("unable to create directory for %s",
+ lock->log_file);
+ oflags |= O_CREAT;
+ }
+
+ logfd = open(lock->log_file, oflags, 0666);
+ if (logfd < 0) {
+ if (!log_all_ref_updates && errno == ENOENT)
+ return 0;
+ return error("Unable to append to %s: %s",
+ lock->log_file, strerror(errno));
+ }
+
+ setup_ident();
+ comitter = git_committer_info(1);
+ if (msg) {
+ maxlen = strlen(comitter) + strlen(msg) + 2*40 + 5;
+ logrec = xmalloc(maxlen);
+ len = snprintf(logrec, maxlen, "%s %s %s\t%s\n",
+ sha1_to_hex(lock->old_sha1),
+ sha1_to_hex(sha1),
+ comitter,
+ msg);
+ } else {
+ maxlen = strlen(comitter) + 2*40 + 4;
+ logrec = xmalloc(maxlen);
+ len = snprintf(logrec, maxlen, "%s %s %s\n",
+ sha1_to_hex(lock->old_sha1),
+ sha1_to_hex(sha1),
+ comitter);
+ }
+ written = len <= maxlen ? write(logfd, logrec, len) : -1;
+ free(logrec);
+ close(logfd);
+ if (written != len)
+ return error("Unable to append to %s", lock->log_file);
+ return 0;
+}
+
+int write_ref_sha1(struct ref_lock *lock,
+ const unsigned char *sha1, const char *logmsg)
+{
+ static char term = '\n';
+
+ if (!lock)
return -1;
- filename = ref_file_name(ref);
- lock_filename = ref_lock_file_name(ref);
- if (safe_create_leading_directories(filename))
- die("unable to create leading directory for %s", filename);
- fd = open(lock_filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
- if (fd < 0) {
- error("Writing %s", lock_filename);
- perror("Open");
+ if (!lock->force_write && !memcmp(lock->old_sha1, sha1, 20)) {
+ unlock_ref(lock);
+ return 0;
}
- retval = write_ref_file(filename, lock_filename, fd, sha1);
- free(filename);
- free(lock_filename);
- return retval;
+ if (write(lock->lock_fd, sha1_to_hex(sha1), 40) != 40 ||
+ write(lock->lock_fd, &term, 1) != 1
+ || close(lock->lock_fd) < 0) {
+ error("Couldn't write %s", lock->lock_file);
+ unlock_ref(lock);
+ return -1;
+ }
+ if (log_ref_write(lock, sha1, logmsg) < 0) {
+ unlock_ref(lock);
+ return -1;
+ }
+ if (rename(lock->lock_file, lock->ref_file) < 0) {
+ error("Couldn't set %s", lock->ref_file);
+ unlock_ref(lock);
+ return -1;
+ }
+ lock->lock_fd = -1;
+ unlock_ref(lock);
+ return 0;
+}
+
+int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)
+{
+ const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec;
+ char *tz_c;
+ int logfd, tz;
+ struct stat st;
+ unsigned long date;
+ unsigned char logged_sha1[20];
+
+ logfile = git_path("logs/%s", ref);
+ logfd = open(logfile, O_RDONLY, 0);
+ if (logfd < 0)
+ die("Unable to read log %s: %s", logfile, strerror(errno));
+ fstat(logfd, &st);
+ if (!st.st_size)
+ die("Log %s is empty.", logfile);
+ logdata = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, logfd, 0);
+ close(logfd);
+
+ lastrec = NULL;
+ rec = logend = logdata + st.st_size;
+ while (logdata < rec) {
+ if (logdata < rec && *(rec-1) == '\n')
+ rec--;
+ lastgt = NULL;
+ while (logdata < rec && *(rec-1) != '\n') {
+ rec--;
+ if (*rec == '>')
+ lastgt = rec;
+ }
+ if (!lastgt)
+ die("Log %s is corrupt.", logfile);
+ date = strtoul(lastgt + 1, &tz_c, 10);
+ if (date <= at_time) {
+ if (lastrec) {
+ if (get_sha1_hex(lastrec, logged_sha1))
+ die("Log %s is corrupt.", logfile);
+ if (get_sha1_hex(rec + 41, sha1))
+ die("Log %s is corrupt.", logfile);
+ if (memcmp(logged_sha1, sha1, 20)) {
+ tz = strtoul(tz_c, NULL, 10);
+ fprintf(stderr,
+ "warning: Log %s has gap after %s.\n",
+ logfile, show_rfc2822_date(date, tz));
+ }
+ } else if (date == at_time) {
+ if (get_sha1_hex(rec + 41, sha1))
+ die("Log %s is corrupt.", logfile);
+ } else {
+ if (get_sha1_hex(rec + 41, logged_sha1))
+ die("Log %s is corrupt.", logfile);
+ if (memcmp(logged_sha1, sha1, 20)) {
+ tz = strtoul(tz_c, NULL, 10);
+ fprintf(stderr,
+ "warning: Log %s unexpectedly ended on %s.\n",
+ logfile, show_rfc2822_date(date, tz));
+ }
+ }
+ munmap((void*)logdata, st.st_size);
+ return 0;
+ }
+ lastrec = rec;
+ }
+
+ rec = logdata;
+ while (rec < logend && *rec != '>' && *rec != '\n')
+ rec++;
+ if (rec == logend || *rec == '\n')
+ die("Log %s is corrupt.", logfile);
+ date = strtoul(rec + 1, &tz_c, 10);
+ tz = strtoul(tz_c, NULL, 10);
+ if (get_sha1_hex(logdata, sha1))
+ die("Log %s is corrupt.", logfile);
+ munmap((void*)logdata, st.st_size);
+ fprintf(stderr, "warning: Log %s only goes back to %s.\n",
+ logfile, show_rfc2822_date(date, tz));
+ return 0;
}
diff --git a/refs.h b/refs.h
index fa816c1e9f..6c946eabcf 100644
--- a/refs.h
+++ b/refs.h
@@ -1,6 +1,15 @@
#ifndef REFS_H
#define REFS_H
+struct ref_lock {
+ char *ref_file;
+ char *lock_file;
+ char *log_file;
+ unsigned char old_sha1[20];
+ int lock_fd;
+ int force_write;
+};
+
/*
* Calls the specified function for each ref file until it returns nonzero,
* and returns the value
@@ -14,16 +23,20 @@ extern int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *
/** Reads the refs file specified into sha1 **/
extern int get_ref_sha1(const char *ref, unsigned char *sha1);
-/** Locks ref and returns the fd to give to write_ref_sha1() if the ref
- * has the given value currently; otherwise, returns -1.
- **/
-extern int lock_ref_sha1(const char *ref, const unsigned char *old_sha1);
+/** Locks a "refs/" ref returning the lock on success and NULL on failure. **/
+extern struct ref_lock* lock_ref_sha1(const char *ref, const unsigned char *old_sha1, int mustexist);
+
+/** Locks any ref (for 'HEAD' type refs). */
+extern struct ref_lock* lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int mustexist);
+
+/** Release any lock taken but not written. **/
+extern void unlock_ref (struct ref_lock *lock);
-/** Writes sha1 into the refs file specified, locked with the given fd. **/
-extern int write_ref_sha1(const char *ref, int fd, const unsigned char *sha1);
+/** Writes sha1 into the ref specified by the lock. **/
+extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);
-/** Writes sha1 into the refs file specified. **/
-extern int write_ref_sha1_unlocked(const char *ref, const unsigned char *sha1);
+/** Reads log for the value of ref during at_time. **/
+extern int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1);
/** Returns 0 if target has the right format for a ref. **/
extern int check_ref_format(const char *target);
diff --git a/sha1_name.c b/sha1_name.c
index dc6835520c..fbbde1cf7d 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -4,6 +4,7 @@
#include "tree.h"
#include "blob.h"
#include "tree-walk.h"
+#include "refs.h"
static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
{
@@ -245,36 +246,61 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
"refs/remotes/%.*s/HEAD",
NULL
};
- const char **p;
- const char *warning = "warning: refname '%.*s' is ambiguous.\n";
- char *pathname;
- int already_found = 0;
+ static const char *warning = "warning: refname '%.*s' is ambiguous.\n";
+ const char **p, *pathname;
+ char *real_path = NULL;
+ int refs_found = 0, am;
+ unsigned long at_time = (unsigned long)-1;
unsigned char *this_result;
unsigned char sha1_from_ref[20];
if (len == 40 && !get_sha1_hex(str, sha1))
return 0;
+ /* At a given period of time? "@{2 hours ago}" */
+ for (am = 1; am < len - 1; am++) {
+ if (str[am] == '@' && str[am+1] == '{' && str[len-1] == '}') {
+ int date_len = len - am - 3;
+ char *date_spec = xmalloc(date_len + 1);
+ strncpy(date_spec, str + am + 2, date_len);
+ date_spec[date_len] = 0;
+ at_time = approxidate(date_spec);
+ free(date_spec);
+ len = am;
+ break;
+ }
+ }
+
/* Accept only unambiguous ref paths. */
if (ambiguous_path(str, len))
return -1;
for (p = fmt; *p; p++) {
- this_result = already_found ? sha1_from_ref : sha1;
- pathname = git_path(*p, len, str);
- if (!read_ref(pathname, this_result)) {
- if (warn_ambiguous_refs) {
- if (already_found)
- fprintf(stderr, warning, len, str);
- already_found++;
- }
- else
- return 0;
+ this_result = refs_found ? sha1_from_ref : sha1;
+ pathname = resolve_ref(git_path(*p, len, str), this_result, 1);
+ if (pathname) {
+ if (!refs_found++)
+ real_path = strdup(pathname);
+ if (!warn_ambiguous_refs)
+ break;
}
}
- if (already_found)
- return 0;
- return -1;
+
+ if (!refs_found)
+ return -1;
+
+ if (warn_ambiguous_refs && refs_found > 1)
+ fprintf(stderr, warning, len, str);
+
+ if (at_time != (unsigned long)-1) {
+ read_ref_at(
+ real_path + strlen(git_path(".")) - 1,
+ at_time,
+ sha1);
+ }
+
+ free(real_path);
+ return 0;
}
static int get_sha1_1(const char *name, int len, unsigned char *sha1);
@@ -456,7 +482,7 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1)
*/
int get_sha1(const char *name, unsigned char *sha1)
{
- int ret;
+ int ret, bracket_depth;
unsigned unused;
int namelen = strlen(name);
const char *cp;
@@ -502,8 +528,15 @@ int get_sha1(const char *name, unsigned char *sha1)
}
return -1;
}
- cp = strchr(name, ':');
- if (cp) {
+ for (cp = name, bracket_depth = 0; *cp; cp++) {
+ if (*cp == '{')
+ bracket_depth++;
+ else if (bracket_depth && *cp == '}')
+ bracket_depth--;
+ else if (!bracket_depth && *cp == ':')
+ break;
+ }
+ if (*cp == ':') {
unsigned char tree_sha1[20];
if (!get_sha1_1(name, cp-name, tree_sha1))
return get_tree_entry(tree_sha1, cp+1, sha1,
diff --git a/ssh-fetch.c b/ssh-fetch.c
index 4eb9e04829..e3067b878e 100644
--- a/ssh-fetch.c
+++ b/ssh-fetch.c
@@ -132,6 +132,7 @@ int main(int argc, char **argv)
if (!prog) prog = "git-ssh-upload";
setup_git_directory();
+ git_config(git_default_config);
while (arg < argc && argv[arg][0] == '-') {
if (argv[arg][1] == 't') {
@@ -158,6 +159,7 @@ int main(int argc, char **argv)
}
commit_id = argv[arg];
url = argv[arg + 1];
+ write_ref_log_details = url;
if (setup_connection(&fd_in, &fd_out, prog, url, arg, argv + 1))
return 1;
diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
index cf33989b56..2c9bbb59b0 100755
--- a/t/t0000-basic.sh
+++ b/t/t0000-basic.sh
@@ -195,6 +195,20 @@ test_expect_success \
'git-ls-tree -r output for a known tree.' \
'diff current expected'
+test_expect_success \
+ 'writing partial tree out with git-write-tree --prefix.' \
+ 'ptree=$(git-write-tree --prefix=path3)'
+test_expect_success \
+ 'validate object ID for a known tree.' \
+ 'test "$ptree" = 21ae8269cacbe57ae09138dcc3a2887f904d02b3'
+
+test_expect_success \
+ 'writing partial tree out with git-write-tree --prefix.' \
+ 'ptree=$(git-write-tree --prefix=path3/subp3)'
+test_expect_success \
+ 'validate object ID for a known tree.' \
+ 'test "$ptree" = 3c5e5399f3a333eddecce7a9b9465b63f65f51e2'
+
################################################################
rm .git/index
test_expect_success \
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
new file mode 100755
index 0000000000..df3e993365
--- /dev/null
+++ b/t/t1400-update-ref.sh
@@ -0,0 +1,213 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Shawn Pearce
+#
+
+test_description='Test git-update-ref and basic ref logging'
+. ./test-lib.sh
+
+Z=0000000000000000000000000000000000000000
+A=1111111111111111111111111111111111111111
+B=2222222222222222222222222222222222222222
+C=3333333333333333333333333333333333333333
+D=4444444444444444444444444444444444444444
+E=5555555555555555555555555555555555555555
+F=6666666666666666666666666666666666666666
+m=refs/heads/master
+
+test_expect_success \
+ "create $m" \
+ 'git-update-ref $m $A &&
+ test $A = $(cat .git/$m)'
+test_expect_success \
+ "create $m" \
+ 'git-update-ref $m $B $A &&
+ test $B = $(cat .git/$m)'
+rm -f .git/$m
+
+test_expect_success \
+ "create $m (by HEAD)" \
+ 'git-update-ref HEAD $A &&
+ test $A = $(cat .git/$m)'
+test_expect_success \
+ "create $m (by HEAD)" \
+ 'git-update-ref HEAD $B $A &&
+ test $B = $(cat .git/$m)'
+rm -f .git/$m
+
+test_expect_failure \
+ '(not) create HEAD with old sha1' \
+ 'git-update-ref HEAD $A $B'
+test_expect_failure \
+ "(not) prior created .git/$m" \
+ 'test -f .git/$m'
+rm -f .git/$m
+
+test_expect_success \
+ "create HEAD" \
+ 'git-update-ref HEAD $A'
+test_expect_failure \
+ '(not) change HEAD with wrong SHA1' \
+ 'git-update-ref HEAD $B $Z'
+test_expect_failure \
+ "(not) changed .git/$m" \
+ 'test $B = $(cat .git/$m)'
+rm -f .git/$m
+
+mkdir -p .git/logs/refs/heads
+touch .git/logs/refs/heads/master
+test_expect_success \
+ "create $m (logged by touch)" \
+ 'GIT_COMMITTER_DATE="2005-05-26 23:30" \
+ git-update-ref HEAD $A -m "Initial Creation" &&
+ test $A = $(cat .git/$m)'
+test_expect_success \
+ "update $m (logged by touch)" \
+ 'GIT_COMMITTER_DATE="2005-05-26 23:31" \
+ git-update-ref HEAD $B $A -m "Switch" &&
+ test $B = $(cat .git/$m)'
+test_expect_success \
+ "set $m (logged by touch)" \
+ 'GIT_COMMITTER_DATE="2005-05-26 23:41" \
+ git-update-ref HEAD $A &&
+ test $A = $(cat .git/$m)'
+
+cat >expect <<EOF
+$Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 Initial Creation
+$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150260 +0000 Switch
+$B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000
+EOF
+test_expect_success \
+ "verifying $m's log" \
+ 'diff expect .git/logs/$m'
+rm -rf .git/$m .git/logs expect
+
+test_expect_success \
+ 'enable core.logAllRefUpdates' \
+ 'git-repo-config core.logAllRefUpdates true &&
+ test true = $(git-repo-config --bool --get core.logAllRefUpdates)'
+
+test_expect_success \
+ "create $m (logged by config)" \
+ 'GIT_COMMITTER_DATE="2005-05-26 23:32" \
+ git-update-ref HEAD $A -m "Initial Creation" &&
+ test $A = $(cat .git/$m)'
+test_expect_success \
+ "update $m (logged by config)" \
+ 'GIT_COMMITTER_DATE="2005-05-26 23:33" \
+ git-update-ref HEAD $B $A -m "Switch" &&
+ test $B = $(cat .git/$m)'
+test_expect_success \
+ "set $m (logged by config)" \
+ 'GIT_COMMITTER_DATE="2005-05-26 23:43" \
+ git-update-ref HEAD $A &&
+ test $A = $(cat .git/$m)'
+
+cat >expect <<EOF
+$Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 +0000 Initial Creation
+$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 +0000 Switch
+$B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 +0000
+EOF
+test_expect_success \
+ "verifying $m's log" \
+ 'diff expect .git/logs/$m'
+rm -f .git/$m .git/logs/$m expect
+
+git-update-ref $m $D
+cat >.git/logs/$m <<EOF
+$C $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500
+$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 -0500
+$F $Z $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150680 -0500
+$Z $E $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 -0500
+EOF
+
+ed="Thu, 26 May 2005 18:32:00 -0500"
+gd="Thu, 26 May 2005 18:33:00 -0500"
+ld="Thu, 26 May 2005 18:43:00 -0500"
+test_expect_success \
+ 'Query "master@{May 25 2005}" (before history)' \
+ 'rm -f o e
+ git-rev-parse --verify "master@{May 25 2005}" >o 2>e &&
+ test $C = $(cat o) &&
+ test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"'
+test_expect_success \
+ "Query master@{2005-05-25} (before history)" \
+ 'rm -f o e
+ git-rev-parse --verify master@{2005-05-25} >o 2>e &&
+ test $C = $(cat o) &&
+ echo test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"'
+test_expect_success \
+ 'Query "master@{May 26 2005 23:31:59}" (1 second before history)' \
+ 'rm -f o e
+ git-rev-parse --verify "master@{May 26 2005 23:31:59}" >o 2>e &&
+ test $C = $(cat o) &&
+ test "warning: Log .git/logs/$m only goes back to $ed." = "$(cat e)"'
+test_expect_success \
+ 'Query "master@{May 26 2005 23:32:00}" (exactly history start)' \
+ 'rm -f o e
+ git-rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e &&
+ test $A = $(cat o) &&
+ test "" = "$(cat e)"'
+test_expect_success \
+ 'Query "master@{2005-05-26 23:33:01}" (middle of history with gap)' \
+ 'rm -f o e
+ git-rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e &&
+ test $B = $(cat o) &&
+ test "warning: Log .git/logs/$m has gap after $gd." = "$(cat e)"'
+test_expect_success \
+ 'Query "master@{2005-05-26 23:38:00}" (middle of history)' \
+ 'rm -f o e
+ git-rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e &&
+ test $Z = $(cat o) &&
+ test "" = "$(cat e)"'
+test_expect_success \
+ 'Query "master@{2005-05-26 23:43:00}" (exact end of history)' \
+ 'rm -f o e
+ git-rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e &&
+ test $E = $(cat o) &&
+ test "" = "$(cat e)"'
+test_expect_success \
+ 'Query "master@{2005-05-28}" (past end of history)' \
+ 'rm -f o e
+ git-rev-parse --verify "master@{2005-05-28}" >o 2>e &&
+ test $D = $(cat o) &&
+ test "warning: Log .git/logs/$m unexpectedly ended on $ld." = "$(cat e)"'
+
+
+rm -f .git/$m .git/logs/$m expect
+
+test_expect_success \
+ 'creating initial files' \
+ 'echo TEST >F &&
+ git-add F &&
+ GIT_AUTHOR_DATE="2005-05-26 23:30" \
+ GIT_COMMITTER_DATE="2005-05-26 23:30" git-commit -m add -a &&
+ h_TEST=$(git-rev-parse --verify HEAD)
+ echo The other day this did not work. >M &&
+ echo And then Bob told me how to fix it. >>M &&
+ echo OTHER >F &&
+ GIT_AUTHOR_DATE="2005-05-26 23:41" \
+ GIT_COMMITTER_DATE="2005-05-26 23:41" git-commit -F M -a &&
+ h_OTHER=$(git-rev-parse --verify HEAD)
+ rm -f M'
+
+cat >expect <<EOF
+$Z $h_TEST $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 commit: add
+$h_TEST $h_OTHER $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000 commit: The other day this did not work.
+EOF
+test_expect_success \
+ 'git-commit logged updates' \
+ 'diff expect .git/logs/$m'
+unset h_TEST h_OTHER
+
+test_expect_success \
+ 'git-cat-file blob master:F (expect OTHER)' \
+ 'test OTHER = $(git-cat-file blob master:F)'
+test_expect_success \
+ 'git-cat-file blob master@{2005-05-26 23:30}:F (expect TEST)' \
+ 'test TEST = $(git-cat-file blob "master@{2005-05-26 23:30}:F")'
+test_expect_success \
+ 'git-cat-file blob master@{2005-05-26 23:42}:F (expect OTHER)' \
+ 'test OTHER = $(git-cat-file blob "master@{2005-05-26 23:42}:F")'
+
+test_done
diff --git a/t/t2101-update-index-reupdate.sh b/t/t2101-update-index-reupdate.sh
index 77aed8d800..a78ea7f0b0 100755
--- a/t/t2101-update-index-reupdate.sh
+++ b/t/t2101-update-index-reupdate.sh
@@ -8,15 +8,16 @@ test_description='git-update-index --again test.
. ./test-lib.sh
+cat > expected <<\EOF
+100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0 file1
+100644 9db8893856a8a02eaa73470054b7c1c5a7c82e47 0 file2
+EOF
test_expect_success 'update-index --add' \
'echo hello world >file1 &&
echo goodbye people >file2 &&
git-update-index --add file1 file2 &&
git-ls-files -s >current &&
- cmp current - <<\EOF
-100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0 file1
-100644 9db8893856a8a02eaa73470054b7c1c5a7c82e47 0 file2
-EOF'
+ cmp current expected'
test_expect_success 'update-index --again' \
'rm -f file1 &&
@@ -29,20 +30,22 @@ test_expect_success 'update-index --again' \
echo happy - failed as expected
fi &&
git-ls-files -s >current &&
- cmp current - <<\EOF
-100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0 file1
-100644 9db8893856a8a02eaa73470054b7c1c5a7c82e47 0 file2
-EOF'
+ cmp current expected'
+cat > expected <<\EOF
+100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0 file2
+EOF
test_expect_success 'update-index --remove --again' \
'git-update-index --remove --again &&
git-ls-files -s >current &&
- cmp current - <<\EOF
-100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0 file2
-EOF'
+ cmp current expected'
test_expect_success 'first commit' 'git-commit -m initial'
+cat > expected <<\EOF
+100644 53ab446c3f4e42ce9bb728a0ccb283a101be4979 0 dir1/file3
+100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0 file2
+EOF
test_expect_success 'update-index again' \
'mkdir -p dir1 &&
echo hello world >dir1/file3 &&
@@ -52,11 +55,12 @@ test_expect_success 'update-index again' \
echo happy >dir1/file3 &&
git-update-index --again &&
git-ls-files -s >current &&
- cmp current - <<\EOF
-100644 53ab446c3f4e42ce9bb728a0ccb283a101be4979 0 dir1/file3
-100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0 file2
-EOF'
+ cmp current expected'
+cat > expected <<\EOF
+100644 d7fb3f695f06c759dbf3ab00046e7cc2da22d10f 0 dir1/file3
+100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0 file2
+EOF
test_expect_success 'update-index --update from subdir' \
'echo not so happy >file2 &&
cd dir1 &&
@@ -64,19 +68,17 @@ test_expect_success 'update-index --update from subdir' \
git-update-index --again &&
cd .. &&
git-ls-files -s >current &&
- cmp current - <<\EOF
-100644 d7fb3f695f06c759dbf3ab00046e7cc2da22d10f 0 dir1/file3
-100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0 file2
-EOF'
+ cmp current expected'
+cat > expected <<\EOF
+100644 594fb5bb1759d90998e2bf2a38261ae8e243c760 0 dir1/file3
+100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0 file2
+EOF
test_expect_success 'update-index --update with pathspec' \
'echo very happy >file2 &&
cat file2 >dir1/file3 &&
git-update-index --again dir1/ &&
git-ls-files -s >current &&
- cmp current - <<\EOF
-100644 594fb5bb1759d90998e2bf2a38261ae8e243c760 0 dir1/file3
-100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0 file2
-EOF'
+ cmp current expected'
test_done
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index c3de151942..5b04efc89d 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -14,7 +14,8 @@ test_expect_success \
'prepare an trivial repository' \
'echo Hello > A &&
git-update-index --add A &&
- git-commit -m "Initial commit."'
+ git-commit -m "Initial commit." &&
+ HEAD=$(git-rev-parse --verify HEAD)'
test_expect_success \
'git branch --help should return success now.' \
@@ -32,4 +33,32 @@ test_expect_success \
'git branch a/b/c should create a branch' \
'git-branch a/b/c && test -f .git/refs/heads/a/b/c'
+cat >expect <<EOF
+0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from HEAD
+EOF
+test_expect_success \
+ 'git branch -l d/e/f should create a branch and a log' \
+ 'GIT_COMMITTER_DATE="2005-05-26 23:30" \
+ git-branch -l d/e/f &&
+ test -f .git/refs/heads/d/e/f &&
+ test -f .git/logs/refs/heads/d/e/f &&
+ diff expect .git/logs/refs/heads/d/e/f'
+
+test_expect_success \
+ 'git branch -d d/e/f should delete a branch and a log' \
+ 'git-branch -d d/e/f &&
+ test ! -f .git/refs/heads/d/e/f &&
+ test ! -f .git/logs/refs/heads/d/e/f'
+
+cat >expect <<EOF
+0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 checkout: Created from master^0
+EOF
+test_expect_success \
+ 'git checkout -b g/h/i -l should create a branch and a log' \
+ 'GIT_COMMITTER_DATE="2005-05-26 23:30" \
+ git-checkout -b g/h/i -l master &&
+ test -f .git/refs/heads/g/h/i &&
+ test -f .git/logs/refs/heads/g/h/i &&
+ diff expect .git/logs/refs/heads/g/h/i'
+
test_done
diff --git a/t/t3300-funny-names.sh b/t/t3300-funny-names.sh
index 72a93da08c..c12270efab 100755
--- a/t/t3300-funny-names.sh
+++ b/t/t3300-funny-names.sh
@@ -40,9 +40,11 @@ test_expect_success 'git-ls-files no-funny' \
t0=`git-write-tree`
echo "$t0" >t0
-echo 'just space
+cat > expected <<\EOF
+just space
no-funny
-"tabs\t,\" (dq) and spaces"' >expected
+"tabs\t,\" (dq) and spaces"
+EOF
test_expect_success 'git-ls-files with-funny' \
'git-update-index --add "$p1" &&
git-ls-files >current &&
@@ -58,14 +60,18 @@ test_expect_success 'git-ls-files -z with-funny' \
t1=`git-write-tree`
echo "$t1" >t1
-echo 'just space
+cat > expected <<\EOF
+just space
no-funny
-"tabs\t,\" (dq) and spaces"' >expected
+"tabs\t,\" (dq) and spaces"
+EOF
test_expect_success 'git-ls-tree with funny' \
'git-ls-tree -r $t1 | sed -e "s/^[^ ]* //" >current &&
diff -u expected current'
-echo 'A "tabs\t,\" (dq) and spaces"' >expected
+cat > expected <<\EOF
+A "tabs\t,\" (dq) and spaces"
+EOF
test_expect_success 'git-diff-index with-funny' \
'git-diff-index --name-status $t0 >current &&
diff -u expected current'
@@ -84,53 +90,62 @@ test_expect_success 'git-diff-tree -z with-funny' \
'git-diff-tree -z --name-status $t0 $t1 | tr \\0 \\012 >current &&
diff -u expected current'
-echo 'CNUM no-funny "tabs\t,\" (dq) and spaces"' >expected
+cat > expected <<\EOF
+CNUM no-funny "tabs\t,\" (dq) and spaces"
+EOF
test_expect_success 'git-diff-tree -C with-funny' \
'git-diff-tree -C --find-copies-harder --name-status \
$t0 $t1 | sed -e 's/^C[0-9]*/CNUM/' >current &&
diff -u expected current'
-echo 'RNUM no-funny "tabs\t,\" (dq) and spaces"' >expected
+cat > expected <<\EOF
+RNUM no-funny "tabs\t,\" (dq) and spaces"
+EOF
test_expect_success 'git-diff-tree delete with-funny' \
'git-update-index --force-remove "$p0" &&
git-diff-index -M --name-status \
$t0 | sed -e 's/^R[0-9]*/RNUM/' >current &&
diff -u expected current'
-echo 'diff --git a/no-funny "b/tabs\t,\" (dq) and spaces"
+cat > expected <<\EOF
+diff --git a/no-funny "b/tabs\t,\" (dq) and spaces"
similarity index NUM%
rename from no-funny
-rename to "tabs\t,\" (dq) and spaces"' >expected
-
+rename to "tabs\t,\" (dq) and spaces"
+EOF
test_expect_success 'git-diff-tree delete with-funny' \
'git-diff-index -M -p $t0 |
sed -e "s/index [0-9]*%/index NUM%/" >current &&
diff -u expected current'
chmod +x "$p1"
-echo 'diff --git a/no-funny "b/tabs\t,\" (dq) and spaces"
+cat > expected <<\EOF
+diff --git a/no-funny "b/tabs\t,\" (dq) and spaces"
old mode 100644
new mode 100755
similarity index NUM%
rename from no-funny
-rename to "tabs\t,\" (dq) and spaces"' >expected
-
+rename to "tabs\t,\" (dq) and spaces"
+EOF
test_expect_success 'git-diff-tree delete with-funny' \
'git-diff-index -M -p $t0 |
sed -e "s/index [0-9]*%/index NUM%/" >current &&
diff -u expected current'
-echo >expected ' "tabs\t,\" (dq) and spaces"
- 1 files changed, 0 insertions(+), 0 deletions(-)'
+cat >expected <<\EOF
+ "tabs\t,\" (dq) and spaces"
+ 1 files changed, 0 insertions(+), 0 deletions(-)
+EOF
test_expect_success 'git-diff-tree rename with-funny applied' \
'git-diff-index -M -p $t0 |
git-apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
diff -u expected current'
-echo >expected ' no-funny
+cat > expected <<\EOF
+ no-funny
"tabs\t,\" (dq) and spaces"
- 2 files changed, 3 insertions(+), 3 deletions(-)'
-
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+EOF
test_expect_success 'git-diff-tree delete with-funny applied' \
'git-diff-index -p $t0 |
git-apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
diff --git a/t/t4012-diff-binary.sh b/t/t4012-diff-binary.sh
index bdd95c0d3d..323606c65c 100755
--- a/t/t4012-diff-binary.sh
+++ b/t/t4012-diff-binary.sh
@@ -16,25 +16,20 @@ test_expect_success 'prepare repository' \
echo git >c &&
cat b b >d'
-test_expect_success 'diff without --binary' \
- 'git-diff | git-apply --stat --summary >current &&
- cmp current - <<\EOF
+cat > expected <<\EOF
a | 2 +-
b | Bin
c | 2 +-
d | Bin
4 files changed, 2 insertions(+), 2 deletions(-)
-EOF'
+EOF
+test_expect_success 'diff without --binary' \
+ 'git-diff | git-apply --stat --summary >current &&
+ cmp current expected'
test_expect_success 'diff with --binary' \
'git-diff --binary | git-apply --stat --summary >current &&
- cmp current - <<\EOF
- a | 2 +-
- b | Bin
- c | 2 +-
- d | Bin
- 4 files changed, 2 insertions(+), 2 deletions(-)
-EOF'
+ cmp current expected'
# apply needs to be able to skip the binary material correctly
# in order to report the line number of a corrupt patch.
diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh
index 92f12d9cfa..f7625a6f46 100755
--- a/t/t5500-fetch-pack.sh
+++ b/t/t5500-fetch-pack.sh
@@ -12,11 +12,11 @@ test_description='Testing multi_ack pack fetching
# Some convenience functions
-function add () {
- local name=$1
- local text="$@"
- local branch=${name:0:1}
- local parents=""
+add () {
+ name=$1
+ text="$@"
+ branch=`echo $name | sed -e 's/^\(.\).*$/\1/'`
+ parents=""
shift
while test $1; do
@@ -36,13 +36,13 @@ function add () {
eval ${branch}TIP=$commit
}
-function count_objects () {
+count_objects () {
ls .git/objects/??/* 2>>log2.txt | wc -l | tr -d " "
}
-function test_expect_object_count () {
- local message=$1
- local count=$2
+test_expect_object_count () {
+ message=$1
+ count=$2
output="$(count_objects)"
test_expect_success \
@@ -50,18 +50,18 @@ function test_expect_object_count () {
"test $count = $output"
}
-function pull_to_client () {
- local number=$1
- local heads=$2
- local count=$3
- local no_strict_count_check=$4
+pull_to_client () {
+ number=$1
+ heads=$2
+ count=$3
+ no_strict_count_check=$4
cd client
test_expect_success "$number pull" \
"git-fetch-pack -k -v .. $heads"
case "$heads" in *A*) echo $ATIP > .git/refs/heads/A;; esac
case "$heads" in *B*) echo $BTIP > .git/refs/heads/B;; esac
- git-symbolic-ref HEAD refs/heads/${heads:0:1}
+ git-symbolic-ref HEAD refs/heads/`echo $heads | sed -e 's/^\(.\).*$/\1/'`
test_expect_success "fsck" 'git-fsck-objects --full > fsck.txt 2>&1'
diff --git a/t/t6000lib.sh b/t/t6000lib.sh
index c6752af48e..d40262159b 100755
--- a/t/t6000lib.sh
+++ b/t/t6000lib.sh
@@ -69,7 +69,9 @@ on_committer_date()
{
_date=$1
shift 1
- GIT_COMMITTER_DATE=$_date "$@"
+ export GIT_COMMITTER_DATE="$_date"
+ "$@"
+ unset GIT_COMMITTER_DATE
}
# Execute a command and suppress any error output.
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
new file mode 100755
index 0000000000..a61da1efbd
--- /dev/null
+++ b/t/t9001-send-email.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+test_description='git-send-email'
+. ./test-lib.sh
+
+PROG='git send-email'
+test_expect_success \
+ 'prepare reference tree' \
+ 'echo "1A quick brown fox jumps over the" >file &&
+ echo "lazy dog" >>file &&
+ git add file
+ GIT_AUTHOR_NAME="A" git commit -a -m "Initial."'
+
+test_expect_success \
+ 'Setup helper tool' \
+ '(echo "#!/bin/sh"
+ echo shift
+ echo for a
+ echo do
+ echo " echo \"!\$a!\""
+ echo "done >commandline"
+ echo "cat > msgtxt"
+ ) >fake.sendmail
+ chmod +x ./fake.sendmail
+ git add fake.sendmail
+ GIT_AUTHOR_NAME="A" git commit -a -m "Second."'
+
+test_expect_success \
+ 'Extract patches and send' \
+ 'git format-patch -n HEAD^1
+ git send-email -from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" ./0001*txt'
+
+cat >expected <<\EOF
+!nobody@example.com!
+!author@example.com!
+EOF
+test_expect_success \
+ 'Verify commandline' \
+ 'diff commandline expected'
+
+test_done
diff --git a/update-ref.c b/update-ref.c
index fd487421cd..a1e6bb90fe 100644
--- a/update-ref.c
+++ b/update-ref.c
@@ -1,85 +1,56 @@
#include "cache.h"
#include "refs.h"
-static const char git_update_ref_usage[] = "git-update-ref <refname> <value> [<oldval>]";
-
-static int re_verify(const char *path, unsigned char *oldsha1, unsigned char *currsha1)
-{
- char buf[40];
- int fd = open(path, O_RDONLY), nr;
- if (fd < 0)
- return -1;
- nr = read(fd, buf, 40);
- close(fd);
- if (nr != 40 || get_sha1_hex(buf, currsha1) < 0)
- return -1;
- return memcmp(oldsha1, currsha1, 20) ? -1 : 0;
-}
+static const char git_update_ref_usage[] =
+"git-update-ref <refname> <value> [<oldval>] [-m <reason>]";
int main(int argc, char **argv)
{
- char *hex;
- const char *refname, *value, *oldval, *path;
- char *lockpath;
- unsigned char sha1[20], oldsha1[20], currsha1[20];
- int fd, written;
+ const char *refname=NULL, *value=NULL, *oldval=NULL, *msg=NULL;
+ struct ref_lock *lock;
+ unsigned char sha1[20], oldsha1[20];
+ int i;
setup_git_directory();
git_config(git_default_config);
- if (argc < 3 || argc > 4)
+
+ for (i = 1; i < argc; i++) {
+ if (!strcmp("-m", argv[i])) {
+ if (i+1 >= argc)
+ usage(git_update_ref_usage);
+ msg = argv[++i];
+ if (!*msg)
+ die("Refusing to perform update with empty message.");
+ if (strchr(msg, '\n'))
+ die("Refusing to perform update with \\n in message.");
+ continue;
+ }
+ if (!refname) {
+ refname = argv[i];
+ continue;
+ }
+ if (!value) {
+ value = argv[i];
+ continue;
+ }
+ if (!oldval) {
+ oldval = argv[i];
+ continue;
+ }
+ }
+ if (!refname || !value)
usage(git_update_ref_usage);
- refname = argv[1];
- value = argv[2];
- oldval = argv[3];
if (get_sha1(value, sha1))
die("%s: not a valid SHA1", value);
memset(oldsha1, 0, 20);
if (oldval && get_sha1(oldval, oldsha1))
die("%s: not a valid old SHA1", oldval);
- path = resolve_ref(git_path("%s", refname), currsha1, !!oldval);
- if (!path)
- die("No such ref: %s", refname);
-
- if (oldval) {
- if (memcmp(currsha1, oldsha1, 20))
- die("Ref %s is at %s but expected %s", refname, sha1_to_hex(currsha1), sha1_to_hex(oldsha1));
- /* Nothing to do? */
- if (!memcmp(oldsha1, sha1, 20))
- exit(0);
- }
- path = strdup(path);
- lockpath = mkpath("%s.lock", path);
- if (safe_create_leading_directories(lockpath) < 0)
- die("Unable to create all of %s", lockpath);
-
- fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666);
- if (fd < 0)
- die("Unable to create %s", lockpath);
- hex = sha1_to_hex(sha1);
- hex[40] = '\n';
- written = write(fd, hex, 41);
- close(fd);
- if (written != 41) {
- unlink(lockpath);
- die("Unable to write to %s", lockpath);
- }
-
- /*
- * Re-read the ref after getting the lock to verify
- */
- if (oldval && re_verify(path, oldsha1, currsha1) < 0) {
- unlink(lockpath);
- die("Ref lock failed");
- }
-
- /*
- * Finally, replace the old ref with the new one
- */
- if (rename(lockpath, path) < 0) {
- unlink(lockpath);
- die("Unable to create %s", path);
- }
+ lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL, 0);
+ if (!lock)
+ return 1;
+ if (write_ref_sha1(lock, sha1, msg) < 0)
+ return 1;
return 0;
}
diff --git a/write-tree.c b/write-tree.c
index 7a4f691d8a..895e7a359d 100644
--- a/write-tree.c
+++ b/write-tree.c
@@ -8,8 +8,10 @@
#include "cache-tree.h"
static int missing_ok = 0;
+static char *prefix = NULL;
-static const char write_tree_usage[] = "git-write-tree [--missing-ok]";
+static const char write_tree_usage[] =
+"git-write-tree [--missing-ok] [--prefix=<prefix>/]";
static struct cache_file cache_file;
@@ -21,13 +23,18 @@ int main(int argc, char **argv)
newfd = hold_index_file_for_update(&cache_file, get_index_file());
entries = read_cache();
- if (argc == 2) {
- if (!strcmp(argv[1], "--missing-ok"))
+
+ while (1 < argc) {
+ char *arg = argv[1];
+ if (!strcmp(arg, "--missing-ok"))
missing_ok = 1;
+ else if (!strncmp(arg, "--prefix=", 9))
+ prefix = arg + 9;
else
die(write_tree_usage);
+ argc--; argv++;
}
-
+
if (argc > 2)
die("too many options");
@@ -54,6 +61,12 @@ int main(int argc, char **argv)
* performance penalty and not a big deal.
*/
}
- printf("%s\n", sha1_to_hex(active_cache_tree->sha1));
+ if (prefix) {
+ struct cache_tree *subtree =
+ cache_tree_find(active_cache_tree, prefix);
+ printf("%s\n", sha1_to_hex(subtree->sha1));
+ }
+ else
+ printf("%s\n", sha1_to_hex(active_cache_tree->sha1));
return 0;
}