summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/config.txt8
-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--blame.c4
-rw-r--r--builtin-grep.c26
-rw-r--r--builtin-ls-tree.c2
-rw-r--r--builtin-read-tree.c140
-rw-r--r--builtin-rev-list.c23
-rw-r--r--builtin-tar-tree.c21
-rw-r--r--cache-tree.c26
-rw-r--r--cache-tree.h2
-rw-r--r--cache.h1
-rw-r--r--config.c5
-rw-r--r--environment.c1
-rw-r--r--fetch-pack.c16
-rw-r--r--fetch.c76
-rw-r--r--fetch.h3
-rw-r--r--fsck-objects.c68
-rwxr-xr-xgit-am.sh2
-rwxr-xr-xgit-applypatch.sh2
-rwxr-xr-xgit-branch.sh14
-rwxr-xr-xgit-checkout.sh19
-rwxr-xr-xgit-commit.sh3
-rwxr-xr-xgit-reset.sh2
-rwxr-xr-xgit-svnimport.perl11
-rw-r--r--http-fetch.c2
-rw-r--r--http-push.c24
-rw-r--r--local-fetch.c2
-rw-r--r--object.c7
-rw-r--r--pack-objects.c27
-rw-r--r--refs.c374
-rw-r--r--refs.h29
-rw-r--r--revision.c26
-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/t3200-branch.sh31
-rw-r--r--tree-walk.c33
-rw-r--r--tree-walk.h5
-rw-r--r--tree.c124
-rw-r--r--tree.h20
-rw-r--r--update-ref.c103
-rw-r--r--write-tree.c23
49 files changed, 1216 insertions, 473 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/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/blame.c b/blame.c
index 99ceea81df..88bfec262f 100644
--- a/blame.c
+++ b/blame.c
@@ -149,7 +149,7 @@ static void free_patch(struct patch *p)
free(p);
}
-static int get_blob_sha1_internal(unsigned char *sha1, const char *base,
+static int get_blob_sha1_internal(const unsigned char *sha1, const char *base,
int baselen, const char *pathname,
unsigned mode, int stage);
@@ -178,7 +178,7 @@ static int get_blob_sha1(struct tree *t, const char *pathname,
return 0;
}
-static int get_blob_sha1_internal(unsigned char *sha1, const char *base,
+static int get_blob_sha1_internal(const unsigned char *sha1, const char *base,
int baselen, const char *pathname,
unsigned mode, int stage)
{
diff --git a/builtin-grep.c b/builtin-grep.c
index 53de8a8836..acc4eea363 100644
--- a/builtin-grep.c
+++ b/builtin-grep.c
@@ -578,11 +578,9 @@ static int grep_tree(struct grep_opt *opt, const char **paths,
struct tree_desc *tree,
const char *tree_name, const char *base)
{
- unsigned mode;
int len;
int hit = 0;
- const char *path;
- const unsigned char *sha1;
+ struct name_entry entry;
char *down;
char *path_buf = xmalloc(PATH_MAX + strlen(tree_name) + 100);
@@ -597,36 +595,32 @@ static int grep_tree(struct grep_opt *opt, const char **paths,
}
len = strlen(path_buf);
- while (tree->size) {
- int pathlen;
- sha1 = tree_entry_extract(tree, &path, &mode);
- pathlen = strlen(path);
- strcpy(path_buf + len, path);
+ while (tree_entry(tree, &entry)) {
+ strcpy(path_buf + len, entry.path);
- if (S_ISDIR(mode))
+ if (S_ISDIR(entry.mode))
/* Match "abc/" against pathspec to
* decide if we want to descend into "abc"
* directory.
*/
- strcpy(path_buf + len + pathlen, "/");
+ strcpy(path_buf + len + entry.pathlen, "/");
if (!pathspec_matches(paths, down))
;
- else if (S_ISREG(mode))
- hit |= grep_sha1(opt, sha1, path_buf);
- else if (S_ISDIR(mode)) {
+ else if (S_ISREG(entry.mode))
+ hit |= grep_sha1(opt, entry.sha1, path_buf);
+ else if (S_ISDIR(entry.mode)) {
char type[20];
struct tree_desc sub;
void *data;
- data = read_sha1_file(sha1, type, &sub.size);
+ data = read_sha1_file(entry.sha1, type, &sub.size);
if (!data)
die("unable to read tree (%s)",
- sha1_to_hex(sha1));
+ sha1_to_hex(entry.sha1));
sub.buf = data;
hit |= grep_tree(opt, paths, &sub, tree_name, down);
free(data);
}
- update_tree_entry(tree);
}
return hit;
}
diff --git a/builtin-ls-tree.c b/builtin-ls-tree.c
index 48385d59f6..b8d0d88ba8 100644
--- a/builtin-ls-tree.c
+++ b/builtin-ls-tree.c
@@ -53,7 +53,7 @@ static int show_recursive(const char *base, int baselen, const char *pathname)
}
}
-static int show_tree(unsigned char *sha1, const char *base, int baselen,
+static int show_tree(const unsigned char *sha1, const char *base, int baselen,
const char *pathname, unsigned mode, int stage)
{
int retval = 0;
diff --git a/builtin-read-tree.c b/builtin-read-tree.c
index 716f792514..0c6ba3d8a5 100644
--- a/builtin-read-tree.c
+++ b/builtin-read-tree.c
@@ -9,6 +9,7 @@
#include "object.h"
#include "tree.h"
+#include "tree-walk.h"
#include "cache-tree.h"
#include <sys/time.h>
#include <signal.h>
@@ -23,13 +24,24 @@ 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;
static struct object_list *trees = NULL;
-static struct cache_entry df_conflict_entry = {
+static struct cache_entry df_conflict_entry = {
+};
+
+struct tree_entry_list {
+ struct tree_entry_list *next;
+ unsigned directory : 1;
+ unsigned executable : 1;
+ unsigned symlink : 1;
+ unsigned int mode;
+ const char *name;
+ const unsigned char *sha1;
};
static struct tree_entry_list df_conflict_list = {
@@ -39,7 +51,35 @@ static struct tree_entry_list df_conflict_list = {
typedef int (*merge_fn_t)(struct cache_entry **src);
-static int entcmp(char *name1, int dir1, char *name2, int dir2)
+static struct tree_entry_list *create_tree_entry_list(struct tree *tree)
+{
+ struct tree_desc desc;
+ struct name_entry one;
+ struct tree_entry_list *ret = NULL;
+ struct tree_entry_list **list_p = &ret;
+
+ desc.buf = tree->buffer;
+ desc.size = tree->size;
+
+ while (tree_entry(&desc, &one)) {
+ struct tree_entry_list *entry;
+
+ entry = xmalloc(sizeof(struct tree_entry_list));
+ entry->name = one.path;
+ entry->sha1 = one.sha1;
+ entry->mode = one.mode;
+ entry->directory = S_ISDIR(one.mode) != 0;
+ entry->executable = (one.mode & S_IXUSR) != 0;
+ entry->symlink = S_ISLNK(one.mode) != 0;
+ entry->next = NULL;
+
+ *list_p = entry;
+ list_p = &entry->next;
+ }
+ return ret;
+}
+
+static int entcmp(const char *name1, int dir1, const char *name2, int dir2)
{
int len1 = strlen(name1);
int len2 = strlen(name2);
@@ -67,7 +107,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
int src_size = len + 1;
do {
int i;
- char *first;
+ const char *first;
int firstdir = 0;
int pathlen;
unsigned ce_size;
@@ -161,9 +201,10 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
}
if (posns[i]->directory) {
+ struct tree *tree = lookup_tree(posns[i]->sha1);
any_dirs = 1;
- parse_tree(posns[i]->item.tree);
- subposns[i] = posns[i]->item.tree->entries;
+ parse_tree(tree);
+ subposns[i] = create_tree_entry_list(tree);
posns[i] = posns[i]->next;
src[i + merge] = &df_conflict_entry;
continue;
@@ -187,7 +228,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
any_files = 1;
- memcpy(ce->sha1, posns[i]->item.any->sha1, 20);
+ memcpy(ce->sha1, posns[i]->sha1, 20);
src[i + merge] = ce;
subposns[i] = &df_conflict_list;
posns[i] = posns[i]->next;
@@ -368,10 +409,11 @@ static int unpack_trees(merge_fn_t fn)
if (len) {
posns = xmalloc(len * sizeof(struct tree_entry_list *));
for (i = 0; i < len; i++) {
- posns[i] = ((struct tree *) posn->item)->entries;
+ 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;
}
@@ -721,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:
@@ -735,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;
@@ -774,19 +840,23 @@ static int read_cache_unmerged(void)
static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree)
{
- struct tree_entry_list *ent;
+ struct tree_desc desc;
+ struct name_entry entry;
int cnt;
memcpy(it->sha1, tree->object.sha1, 20);
- for (cnt = 0, ent = tree->entries; ent; ent = ent->next) {
- if (!ent->directory)
+ desc.buf = tree->buffer;
+ desc.size = tree->size;
+ cnt = 0;
+ while (tree_entry(&desc, &entry)) {
+ if (!S_ISDIR(entry.mode))
cnt++;
else {
struct cache_tree_sub *sub;
- struct tree *subtree = (struct tree *)ent->item.tree;
+ struct tree *subtree = lookup_tree(entry.sha1);
if (!subtree->object.parsed)
parse_tree(subtree);
- sub = cache_tree_sub(it, ent->name);
+ sub = cache_tree_sub(it, entry.path);
sub->cache_tree = cache_tree();
prime_cache_tree_rec(sub->cache_tree, subtree);
cnt += sub->cache_tree->entry_count;
@@ -805,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;
@@ -850,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;
@@ -873,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");
@@ -895,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/builtin-rev-list.c b/builtin-rev-list.c
index 5277d3cf12..17c04b962d 100644
--- a/builtin-rev-list.c
+++ b/builtin-rev-list.c
@@ -113,7 +113,8 @@ static struct object_list **process_tree(struct tree *tree,
const char *name)
{
struct object *obj = &tree->object;
- struct tree_entry_list *entry;
+ struct tree_desc desc;
+ struct name_entry entry;
struct name_path me;
if (!revs.tree_objects)
@@ -128,18 +129,18 @@ static struct object_list **process_tree(struct tree *tree,
me.up = path;
me.elem = name;
me.elem_len = strlen(name);
- entry = tree->entries;
- tree->entries = NULL;
- while (entry) {
- struct tree_entry_list *next = entry->next;
- if (entry->directory)
- p = process_tree(entry->item.tree, p, &me, entry->name);
+
+ desc.buf = tree->buffer;
+ desc.size = tree->size;
+
+ while (tree_entry(&desc, &entry)) {
+ if (S_ISDIR(entry.mode))
+ p = process_tree(lookup_tree(entry.sha1), p, &me, name);
else
- p = process_blob(entry->item.blob, p, &me, entry->name);
- free(entry->name);
- free(entry);
- entry = next;
+ p = process_blob(lookup_blob(entry.sha1), p, &me, name);
}
+ free(tree->buffer);
+ tree->buffer = NULL;
return p;
}
diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c
index 2d5e06fb69..5f740cf702 100644
--- a/builtin-tar-tree.c
+++ b/builtin-tar-tree.c
@@ -271,30 +271,25 @@ static void write_global_extended_header(const unsigned char *sha1)
static void traverse_tree(struct tree_desc *tree, struct strbuf *path)
{
int pathlen = path->len;
+ struct name_entry entry;
- while (tree->size) {
- const char *name;
- const unsigned char *sha1;
- unsigned mode;
+ while (tree_entry(tree, &entry)) {
void *eltbuf;
char elttype[20];
unsigned long eltsize;
- sha1 = tree_entry_extract(tree, &name, &mode);
- update_tree_entry(tree);
-
- eltbuf = read_sha1_file(sha1, elttype, &eltsize);
+ eltbuf = read_sha1_file(entry.sha1, elttype, &eltsize);
if (!eltbuf)
- die("cannot read %s", sha1_to_hex(sha1));
+ die("cannot read %s", sha1_to_hex(entry.sha1));
path->len = pathlen;
- strbuf_append_string(path, name);
- if (S_ISDIR(mode))
+ strbuf_append_string(path, entry.path);
+ if (S_ISDIR(entry.mode))
strbuf_append_string(path, "/");
- write_entry(sha1, path, mode, eltbuf, eltsize);
+ write_entry(entry.sha1, path, entry.mode, eltbuf, eltsize);
- if (S_ISDIR(mode)) {
+ if (S_ISDIR(entry.mode)) {
struct tree_desc subtree;
subtree.buf = eltbuf;
subtree.size = eltsize;
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/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 f7f8902580..e040ef97b6 100644
--- a/fetch.c
+++ b/fetch.c
@@ -3,11 +3,13 @@
#include "cache.h"
#include "commit.h"
#include "tree.h"
+#include "tree-walk.h"
#include "tag.h"
#include "blob.h"
#include "refs.h"
const char *write_ref = NULL;
+const char *write_ref_log_details = NULL;
int get_tree = 0;
int get_history = 0;
@@ -36,21 +38,33 @@ static int process(struct object *obj);
static int process_tree(struct tree *tree)
{
- struct tree_entry_list *entry;
+ struct tree_desc desc;
+ struct name_entry entry;
if (parse_tree(tree))
return -1;
- entry = tree->entries;
- tree->entries = NULL;
- while (entry) {
- struct tree_entry_list *next = entry->next;
- if (process(entry->item.any))
+ desc.buf = tree->buffer;
+ desc.size = tree->size;
+ while (tree_entry(&desc, &entry)) {
+ struct object *obj = NULL;
+
+ if (S_ISDIR(entry.mode)) {
+ struct tree *tree = lookup_tree(entry.sha1);
+ if (tree)
+ obj = &tree->object;
+ }
+ else {
+ struct blob *blob = lookup_blob(entry.sha1);
+ if (blob)
+ obj = &blob->object;
+ }
+ if (!obj || process(obj))
return -1;
- free(entry->name);
- free(entry);
- entry = next;
}
+ free(tree->buffer);
+ tree->buffer = NULL;
+ tree->size = 0;
return 0;
}
@@ -202,23 +216,51 @@ static int mark_complete(const char *path, const unsigned char *sha1)
int pull(char *target)
{
+ struct ref_lock *lock = NULL;
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);
+ if (lock)
+ unlock_ref(lock);
return -1;
- if (loop())
+ }
+ if (process(lookup_unknown_object(sha1))) {
+ if (lock)
+ unlock_ref(lock);
+ return -1;
+ }
+ if (loop()) {
+ if (lock)
+ unlock_ref(lock);
return -1;
-
- if (write_ref)
- write_ref_sha1_unlocked(write_ref, sha1);
+ }
+
+ 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/fsck-objects.c b/fsck-objects.c
index 1922b6d84c..33ce366e99 100644
--- a/fsck-objects.c
+++ b/fsck-objects.c
@@ -9,8 +9,10 @@
#include "refs.h"
#include "pack.h"
#include "cache-tree.h"
+#include "tree-walk.h"
#define REACHABLE 0x0001
+#define SEEN 0x0002
static int show_root = 0;
static int show_tags = 0;
@@ -115,15 +117,15 @@ static void check_connectivity(void)
#define TREE_UNORDERED (-1)
#define TREE_HAS_DUPS (-2)
-static int verify_ordered(struct tree_entry_list *a, struct tree_entry_list *b)
+static int verify_ordered(unsigned mode1, const char *name1, unsigned mode2, const char *name2)
{
- int len1 = strlen(a->name);
- int len2 = strlen(b->name);
+ int len1 = strlen(name1);
+ int len2 = strlen(name2);
int len = len1 < len2 ? len1 : len2;
unsigned char c1, c2;
int cmp;
- cmp = memcmp(a->name, b->name, len);
+ cmp = memcmp(name1, name2, len);
if (cmp < 0)
return 0;
if (cmp > 0)
@@ -134,8 +136,8 @@ static int verify_ordered(struct tree_entry_list *a, struct tree_entry_list *b)
* Now we need to order the next one, but turn
* a '\0' into a '/' for a directory entry.
*/
- c1 = a->name[len];
- c2 = b->name[len];
+ c1 = name1[len];
+ c2 = name2[len];
if (!c1 && !c2)
/*
* git-write-tree used to write out a nonsense tree that has
@@ -143,9 +145,9 @@ static int verify_ordered(struct tree_entry_list *a, struct tree_entry_list *b)
* sure we do not have duplicate entries.
*/
return TREE_HAS_DUPS;
- if (!c1 && a->directory)
+ if (!c1 && S_ISDIR(mode1))
c1 = '/';
- if (!c2 && b->directory)
+ if (!c2 && S_ISDIR(mode2))
c2 = '/';
return c1 < c2 ? 0 : TREE_UNORDERED;
}
@@ -158,17 +160,32 @@ static int fsck_tree(struct tree *item)
int has_bad_modes = 0;
int has_dup_entries = 0;
int not_properly_sorted = 0;
- struct tree_entry_list *entry, *last;
+ struct tree_desc desc;
+ unsigned o_mode;
+ const char *o_name;
+ const unsigned char *o_sha1;
- last = NULL;
- for (entry = item->entries; entry; entry = entry->next) {
- if (strchr(entry->name, '/'))
+ desc.buf = item->buffer;
+ desc.size = item->size;
+
+ o_mode = 0;
+ o_name = NULL;
+ o_sha1 = NULL;
+ while (desc.size) {
+ unsigned mode;
+ const char *name;
+ const unsigned char *sha1;
+
+ sha1 = tree_entry_extract(&desc, &name, &mode);
+
+ if (strchr(name, '/'))
has_full_path = 1;
- has_zero_pad |= entry->zeropad;
+ has_zero_pad |= *(char *)desc.buf == '0';
+ update_tree_entry(&desc);
- switch (entry->mode) {
+ switch (mode) {
/*
- * Standard modes..
+ * Standard modes..
*/
case S_IFREG | 0755:
case S_IFREG | 0644:
@@ -187,8 +204,8 @@ static int fsck_tree(struct tree *item)
has_bad_modes = 1;
}
- if (last) {
- switch (verify_ordered(last, entry)) {
+ if (o_name) {
+ switch (verify_ordered(o_mode, o_name, mode, name)) {
case TREE_UNORDERED:
not_properly_sorted = 1;
break;
@@ -198,17 +215,14 @@ static int fsck_tree(struct tree *item)
default:
break;
}
- free(last->name);
- free(last);
}
- last = entry;
+ o_mode = mode;
+ o_name = name;
+ o_sha1 = sha1;
}
- if (last) {
- free(last->name);
- free(last);
- }
- item->entries = NULL;
+ free(item->buffer);
+ item->buffer = NULL;
retval = 0;
if (has_full_path) {
@@ -278,6 +292,9 @@ static int fsck_sha1(unsigned char *sha1)
struct object *obj = parse_object(sha1);
if (!obj)
return error("%s: object not found", sha1_to_hex(sha1));
+ if (obj->flags & SEEN)
+ return 0;
+ obj->flags |= SEEN;
if (obj->type == blob_type)
return 0;
if (obj->type == tree_type)
@@ -465,6 +482,7 @@ int main(int argc, char **argv)
{
int i, heads;
+ track_object_refs = 1;
setup_git_directory();
for (i = 1; i < argc; i++) {
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-commit.sh b/git-commit.sh
index 15482d21b3..6dd04fd367 100755
--- a/git-commit.sh
+++ b/git-commit.sh
@@ -713,7 +713,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-svnimport.perl b/git-svnimport.perl
index 61f559f0a8..38ac732ca9 100755
--- a/git-svnimport.perl
+++ b/git-svnimport.perl
@@ -63,10 +63,17 @@ my $svn_dir = $ARGV[1];
our @mergerx = ();
if ($opt_m) {
- @mergerx = ( qr/\W(?:from|of|merge|merging|merged) (\w+)/i );
+ my $branch_esc = quotemeta ($branch_name);
+ my $trunk_esc = quotemeta ($trunk_name);
+ @mergerx =
+ (
+ qr!\b(?:merg(?:ed?|ing))\b.*?\b((?:(?<=$branch_esc/)[\w\.\-]+)|(?:$trunk_esc))\b!i,
+ qr!\b(?:from|of)\W+((?:(?<=$branch_esc/)[\w\.\-]+)|(?:$trunk_esc))\b!i,
+ qr!\b(?:from|of)\W+(?:the )?([\w\.\-]+)[-\s]branch\b!i
+ );
}
if ($opt_M) {
- push (@mergerx, qr/$opt_M/);
+ unshift (@mergerx, qr/$opt_M/);
}
# Absolutize filename now, since we will have chdir'ed by the time we
diff --git a/http-fetch.c b/http-fetch.c
index 178f1ee311..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();
diff --git a/http-push.c b/http-push.c
index b4327d9243..b1c018a089 100644
--- a/http-push.c
+++ b/http-push.c
@@ -1704,6 +1704,7 @@ static struct object_list **process_blob(struct blob *blob,
return p;
obj->flags |= SEEN;
+ name = strdup(name);
return add_object(obj, p, path, name);
}
@@ -1713,7 +1714,8 @@ static struct object_list **process_tree(struct tree *tree,
const char *name)
{
struct object *obj = &tree->object;
- struct tree_entry_list *entry;
+ struct tree_desc desc;
+ struct name_entry entry;
struct name_path me;
obj->flags |= LOCAL;
@@ -1724,21 +1726,23 @@ static struct object_list **process_tree(struct tree *tree,
die("bad tree object %s", sha1_to_hex(obj->sha1));
obj->flags |= SEEN;
+ name = strdup(name);
p = add_object(obj, p, NULL, name);
me.up = path;
me.elem = name;
me.elem_len = strlen(name);
- entry = tree->entries;
- tree->entries = NULL;
- while (entry) {
- struct tree_entry_list *next = entry->next;
- if (entry->directory)
- p = process_tree(entry->item.tree, p, &me, entry->name);
+
+ desc.buf = tree->buffer;
+ desc.size = tree->size;
+
+ while (tree_entry(&desc, &entry)) {
+ if (S_ISDIR(entry.mode))
+ p = process_tree(lookup_tree(entry.sha1), p, &me, name);
else
- p = process_blob(entry->item.blob, p, &me, entry->name);
- free(entry);
- entry = next;
+ p = process_blob(lookup_blob(entry.sha1), p, &me, name);
}
+ free(tree->buffer);
+ tree->buffer = NULL;
return p;
}
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/object.c b/object.c
index 4d46e0d5e4..9adc87479b 100644
--- a/object.c
+++ b/object.c
@@ -9,7 +9,7 @@ struct object **objs;
static int nr_objs;
int obj_allocs;
-int track_object_refs = 1;
+int track_object_refs = 0;
static int hashtable_index(const unsigned char *sha1)
{
@@ -200,8 +200,11 @@ struct object *parse_object(const unsigned char *sha1)
obj = &blob->object;
} else if (!strcmp(type, tree_type)) {
struct tree *tree = lookup_tree(sha1);
- parse_tree_buffer(tree, buffer, size);
obj = &tree->object;
+ if (!tree->object.parsed) {
+ parse_tree_buffer(tree, buffer, size);
+ buffer = NULL;
+ }
} else if (!strcmp(type, commit_type)) {
struct commit *commit = lookup_commit(sha1);
parse_commit_buffer(commit, buffer, size);
diff --git a/pack-objects.c b/pack-objects.c
index 77284cfdba..3590cd5e59 100644
--- a/pack-objects.c
+++ b/pack-objects.c
@@ -690,25 +690,20 @@ static void add_pbase_object(struct tree_desc *tree,
const char *name,
int cmplen)
{
- while (tree->size) {
- const unsigned char *sha1;
- const char *entry_name;
- int entry_len;
- unsigned mode;
+ struct name_entry entry;
+
+ while (tree_entry(tree,&entry)) {
unsigned long size;
char type[20];
- sha1 = tree_entry_extract(tree, &entry_name, &mode);
- update_tree_entry(tree);
- entry_len = strlen(entry_name);
- if (entry_len != cmplen ||
- memcmp(entry_name, name, cmplen) ||
- !has_sha1_file(sha1) ||
- sha1_object_info(sha1, type, &size))
+ if (entry.pathlen != cmplen ||
+ memcmp(entry.path, name, cmplen) ||
+ !has_sha1_file(entry.sha1) ||
+ sha1_object_info(entry.sha1, type, &size))
continue;
if (name[cmplen] != '/') {
unsigned hash = name_hash(up, name);
- add_object_entry(sha1, hash, 1);
+ add_object_entry(entry.sha1, hash, 1);
return;
}
if (!strcmp(type, tree_type)) {
@@ -718,15 +713,15 @@ static void add_pbase_object(struct tree_desc *tree,
const char *down = name+cmplen+1;
int downlen = name_cmp_len(down);
- tree = pbase_tree_get(sha1);
+ tree = pbase_tree_get(entry.sha1);
if (!tree)
return;
sub.buf = tree->tree_data;
sub.size = tree->tree_size;
me.up = up;
- me.elem = entry_name;
- me.len = entry_len;
+ me.elem = entry.path;
+ me.len = entry.pathlen;
add_pbase_object(&sub, &me, down, downlen);
pbase_tree_put(tree);
}
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/revision.c b/revision.c
index 42c077a4cb..6a6952cd55 100644
--- a/revision.c
+++ b/revision.c
@@ -53,8 +53,9 @@ static void mark_blob_uninteresting(struct blob *blob)
void mark_tree_uninteresting(struct tree *tree)
{
+ struct tree_desc desc;
+ struct name_entry entry;
struct object *obj = &tree->object;
- struct tree_entry_list *entry;
if (obj->flags & UNINTERESTING)
return;
@@ -63,17 +64,22 @@ void mark_tree_uninteresting(struct tree *tree)
return;
if (parse_tree(tree) < 0)
die("bad tree %s", sha1_to_hex(obj->sha1));
- entry = tree->entries;
- tree->entries = NULL;
- while (entry) {
- struct tree_entry_list *next = entry->next;
- if (entry->directory)
- mark_tree_uninteresting(entry->item.tree);
+
+ desc.buf = tree->buffer;
+ desc.size = tree->size;
+ while (tree_entry(&desc, &entry)) {
+ if (S_ISDIR(entry.mode))
+ mark_tree_uninteresting(lookup_tree(entry.sha1));
else
- mark_blob_uninteresting(entry->item.blob);
- free(entry);
- entry = next;
+ mark_blob_uninteresting(lookup_blob(entry.sha1));
}
+
+ /*
+ * We don't care about the tree any more
+ * after it has been marked uninteresting.
+ */
+ free(tree->buffer);
+ tree->buffer = NULL;
}
void mark_parents_uninteresting(struct commit *commit)
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/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/tree-walk.c b/tree-walk.c
index 3922058271..297c6972b9 100644
--- a/tree-walk.c
+++ b/tree-walk.c
@@ -37,7 +37,7 @@ static void entry_extract(struct tree_desc *t, struct name_entry *a)
void update_tree_entry(struct tree_desc *desc)
{
- void *buf = desc->buf;
+ const void *buf = desc->buf;
unsigned long size = desc->size;
int len = strlen(buf) + 1 + 20;
@@ -63,7 +63,7 @@ static const char *get_mode(const char *str, unsigned int *modep)
const unsigned char *tree_entry_extract(struct tree_desc *desc, const char **pathp, unsigned int *modep)
{
- void *tree = desc->buf;
+ const void *tree = desc->buf;
unsigned long size = desc->size;
int len = strlen(tree)+1;
const unsigned char *sha1 = tree + len;
@@ -78,6 +78,35 @@ const unsigned char *tree_entry_extract(struct tree_desc *desc, const char **pat
return sha1;
}
+int tree_entry(struct tree_desc *desc, struct name_entry *entry)
+{
+ const void *tree = desc->buf, *path;
+ unsigned long len, size = desc->size;
+
+ if (!size)
+ return 0;
+
+ path = get_mode(tree, &entry->mode);
+ if (!path)
+ die("corrupt tree file");
+
+ entry->path = path;
+ len = strlen(path);
+ entry->pathlen = len;
+
+ path += len + 1;
+ entry->sha1 = path;
+
+ path += 20;
+ len = path - tree;
+ if (len > size)
+ die("corrupt tree file");
+
+ desc->buf = path;
+ desc->size = size - len;
+ return 1;
+}
+
void traverse_trees(int n, struct tree_desc *t, const char *base, traverse_callback_t callback)
{
struct name_entry *entry = xmalloc(n*sizeof(*entry));
diff --git a/tree-walk.h b/tree-walk.h
index 47438fe1c0..e57befa4da 100644
--- a/tree-walk.h
+++ b/tree-walk.h
@@ -2,7 +2,7 @@
#define TREE_WALK_H
struct tree_desc {
- void *buf;
+ const void *buf;
unsigned long size;
};
@@ -16,6 +16,9 @@ struct name_entry {
void update_tree_entry(struct tree_desc *);
const unsigned char *tree_entry_extract(struct tree_desc *, const char **, unsigned int *);
+/* Helper function that does both of the above and returns true for success */
+int tree_entry(struct tree_desc *, struct name_entry *);
+
void *fill_tree_descriptor(struct tree_desc *desc, const unsigned char *sha1);
typedef void (*traverse_callback_t)(int n, unsigned long mask, struct name_entry *entry, const char *base);
diff --git a/tree.c b/tree.c
index d599fb5e1a..9bbe2da37b 100644
--- a/tree.c
+++ b/tree.c
@@ -3,11 +3,12 @@
#include "blob.h"
#include "commit.h"
#include "tag.h"
+#include "tree-walk.h"
#include <stdlib.h>
const char *tree_type = "tree";
-static int read_one_entry(unsigned char *sha1, const char *base, int baselen, const char *pathname, unsigned mode, int stage)
+static int read_one_entry(const unsigned char *sha1, const char *base, int baselen, const char *pathname, unsigned mode, int stage)
{
int len;
unsigned int size;
@@ -77,19 +78,20 @@ int read_tree_recursive(struct tree *tree,
int stage, const char **match,
read_tree_fn_t fn)
{
- struct tree_entry_list *list;
+ struct tree_desc desc;
+ struct name_entry entry;
+
if (parse_tree(tree))
return -1;
- list = tree->entries;
- while (list) {
- struct tree_entry_list *current = list;
- list = list->next;
- if (!match_tree_entry(base, baselen, current->name,
- current->mode, match))
+
+ desc.buf = tree->buffer;
+ desc.size = tree->size;
+
+ while (tree_entry(&desc, &entry)) {
+ if (!match_tree_entry(base, baselen, entry.path, entry.mode, match))
continue;
- switch (fn(current->item.any->sha1, base, baselen,
- current->name, current->mode, stage)) {
+ switch (fn(entry.sha1, base, baselen, entry.path, entry.mode, stage)) {
case 0:
continue;
case READ_TREE_RECURSIVE:
@@ -97,18 +99,17 @@ int read_tree_recursive(struct tree *tree,
default:
return -1;
}
- if (current->directory) {
+ if (S_ISDIR(entry.mode)) {
int retval;
- int pathlen = strlen(current->name);
char *newbase;
- newbase = xmalloc(baselen + 1 + pathlen);
+ newbase = xmalloc(baselen + 1 + entry.pathlen);
memcpy(newbase, base, baselen);
- memcpy(newbase + baselen, current->name, pathlen);
- newbase[baselen + pathlen] = '/';
- retval = read_tree_recursive(current->item.tree,
+ memcpy(newbase + baselen, entry.path, entry.pathlen);
+ newbase[baselen + entry.pathlen] = '/';
+ retval = read_tree_recursive(lookup_tree(entry.sha1),
newbase,
- baselen + pathlen + 1,
+ baselen + entry.pathlen + 1,
stage, match, fn);
free(newbase);
if (retval)
@@ -143,61 +144,49 @@ struct tree *lookup_tree(const unsigned char *sha1)
return (struct tree *) obj;
}
-int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size)
+static int track_tree_refs(struct tree *item)
{
- void *bufptr = buffer;
- struct tree_entry_list **list_p;
- int n_refs = 0;
+ int n_refs = 0, i;
+ struct object_refs *refs;
+ struct tree_desc desc;
+ struct name_entry entry;
+
+ /* Count how many entries there are.. */
+ desc.buf = item->buffer;
+ desc.size = item->size;
+ while (desc.size) {
+ n_refs++;
+ update_tree_entry(&desc);
+ }
- if (item->object.parsed)
- return 0;
- item->object.parsed = 1;
- list_p = &item->entries;
- while (size) {
+ /* Allocate object refs and walk it again.. */
+ i = 0;
+ refs = alloc_object_refs(n_refs);
+ desc.buf = item->buffer;
+ desc.size = item->size;
+ while (tree_entry(&desc, &entry)) {
struct object *obj;
- struct tree_entry_list *entry;
- int len = 1+strlen(bufptr);
- unsigned char *file_sha1 = bufptr + len;
- char *path = strchr(bufptr, ' ');
- unsigned int mode;
- if (size < len + 20 || !path ||
- sscanf(bufptr, "%o", &mode) != 1)
- return -1;
- entry = xmalloc(sizeof(struct tree_entry_list));
- entry->name = strdup(path + 1);
- entry->directory = S_ISDIR(mode) != 0;
- entry->executable = (mode & S_IXUSR) != 0;
- entry->symlink = S_ISLNK(mode) != 0;
- entry->zeropad = *(char *)bufptr == '0';
- entry->mode = mode;
- entry->next = NULL;
-
- bufptr += len + 20;
- size -= len + 20;
-
- if (entry->directory) {
- entry->item.tree = lookup_tree(file_sha1);
- obj = &entry->item.tree->object;
- } else {
- entry->item.blob = lookup_blob(file_sha1);
- obj = &entry->item.blob->object;
- }
- if (obj)
- n_refs++;
- *list_p = entry;
- list_p = &entry->next;
+ if (S_ISDIR(entry.mode))
+ obj = &lookup_tree(entry.sha1)->object;
+ else
+ obj = &lookup_blob(entry.sha1)->object;
+ refs->ref[i++] = obj;
}
+ set_object_refs(&item->object, refs);
+ return 0;
+}
- if (track_object_refs) {
- struct tree_entry_list *entry;
- unsigned i = 0;
- struct object_refs *refs = alloc_object_refs(n_refs);
- for (entry = item->entries; entry; entry = entry->next)
- refs->ref[i++] = entry->item.any;
- set_object_refs(&item->object, refs);
- }
+int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size)
+{
+ if (item->object.parsed)
+ return 0;
+ item->object.parsed = 1;
+ item->buffer = buffer;
+ item->size = size;
+ if (track_object_refs)
+ track_tree_refs(item);
return 0;
}
@@ -206,7 +195,6 @@ int parse_tree(struct tree *item)
char type[20];
void *buffer;
unsigned long size;
- int ret;
if (item->object.parsed)
return 0;
@@ -219,9 +207,7 @@ int parse_tree(struct tree *item)
return error("Object %s not a tree",
sha1_to_hex(item->object.sha1));
}
- ret = parse_tree_buffer(item, buffer, size);
- free(buffer);
- return ret;
+ return parse_tree_buffer(item, buffer, size);
}
struct tree *parse_tree_indirect(const unsigned char *sha1)
diff --git a/tree.h b/tree.h
index 330ab64bbd..dd25c539ef 100644
--- a/tree.h
+++ b/tree.h
@@ -5,24 +5,10 @@
extern const char *tree_type;
-struct tree_entry_list {
- struct tree_entry_list *next;
- unsigned directory : 1;
- unsigned executable : 1;
- unsigned symlink : 1;
- unsigned zeropad : 1;
- unsigned int mode;
- char *name;
- union {
- struct object *any;
- struct tree *tree;
- struct blob *blob;
- } item;
-};
-
struct tree {
struct object object;
- struct tree_entry_list *entries;
+ void *buffer;
+ unsigned long size;
};
struct tree *lookup_tree(const unsigned char *sha1);
@@ -35,7 +21,7 @@ int parse_tree(struct tree *tree);
struct tree *parse_tree_indirect(const unsigned char *sha1);
#define READ_TREE_RECURSIVE 1
-typedef int (*read_tree_fn_t)(unsigned char *, const char *, int, const char *, unsigned int, int);
+typedef int (*read_tree_fn_t)(const unsigned char *, const char *, int, const char *, unsigned int, int);
extern int read_tree_recursive(struct tree *tree,
const char *base, int baselen,
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;
}