From 8b649e27dd26608098605ded691b497ffa032500 Mon Sep 17 00:00:00 2001 From: "H. Peter Anvin" Date: Mon, 14 Nov 2005 22:12:17 -0800 Subject: [PATCH 01/32] git-core-foo -> git-foo, except the core package This patch renames the tarball "git" rather than "git-core", and changes the names of various packages from git-core-foo to git-foo. git-core is still the true core package; an empty RPM package named "git" pulls in ALL the git packages -- this makes updates work correctly, and allows "yum install git" to do the obvious thing. It also renames the git-(core-)tk package to gitk. Signed-off-by: H. Peter Anvin Signed-off-by: Junio C Hamano --- Makefile | 14 ++++++------- git-core.spec.in => git.spec.in | 36 +++++++++++++++++++++++++++------ 2 files changed, 37 insertions(+), 13 deletions(-) rename git-core.spec.in => git.spec.in (84%) diff --git a/Makefile b/Makefile index 63cb99847f..b10a31550a 100644 --- a/Makefile +++ b/Makefile @@ -446,20 +446,20 @@ install-doc: ### Maintainer's dist rules -git-core.spec: git-core.spec.in Makefile +git.spec: git.spec.in Makefile sed -e 's/@@VERSION@@/$(GIT_VERSION)/g' < $< > $@ -GIT_TARNAME=git-core-$(GIT_VERSION) -dist: git-core.spec git-tar-tree +GIT_TARNAME=git-$(GIT_VERSION) +dist: git.spec git-tar-tree ./git-tar-tree HEAD $(GIT_TARNAME) > $(GIT_TARNAME).tar @mkdir -p $(GIT_TARNAME) - @cp git-core.spec $(GIT_TARNAME) - $(TAR) rf $(GIT_TARNAME).tar $(GIT_TARNAME)/git-core.spec + @cp git.spec $(GIT_TARNAME) + $(TAR) rf $(GIT_TARNAME).tar $(GIT_TARNAME)/git.spec @rm -rf $(GIT_TARNAME) gzip -f -9 $(GIT_TARNAME).tar rpm: dist - $(RPMBUILD) -ta git-core-$(GIT_VERSION).tar.gz + $(RPMBUILD) -ta $(GIT_TARNAME).tar.gz deb: dist rm -rf $(GIT_TARNAME) @@ -472,7 +472,7 @@ deb: dist clean: rm -f *.o mozilla-sha1/*.o ppc/*.o compat/*.o $(PROGRAMS) $(LIB_FILE) rm -f $(filter-out gitk,$(SCRIPTS)) - rm -f git-core.spec *.pyc *.pyo + rm -f *.spec *.pyc *.pyo rm -rf $(GIT_TARNAME) rm -f $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz rm -f git-core_$(GIT_VERSION)-*.dsc diff --git a/git-core.spec.in b/git.spec.in similarity index 84% rename from git-core.spec.in rename to git.spec.in index 16c626902a..96dfc1de55 100644 --- a/git-core.spec.in +++ b/git.spec.in @@ -1,5 +1,5 @@ # Pass --without docs to rpmbuild if you don't want the documentation -Name: git-core +Name: git Version: @@VERSION@@ Release: 1%{?dist} Summary: Git core and tools @@ -9,7 +9,7 @@ URL: http://kernel.org/pub/software/scm/git/ Source: http://kernel.org/pub/software/scm/git/%{name}-%{version}.tar.gz BuildRequires: zlib-devel >= 1.2, openssl-devel, curl-devel, expat-devel %{!?_without_docs:, xmlto, asciidoc > 6.0.3} BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) -Requires: zlib >= 1.2, rsync, rcs, curl, less, openssh-clients, python >= 2.3, expat +Requires: git-core, git-svn, git-cvs, git-arch, git-email, gitk %description This is a stupid (but extremely fast) directory content manager. It @@ -19,6 +19,22 @@ distributed source code management system. This package includes rudimentary tools that can be used as a SCM, but you should look elsewhere for tools for ordinary humans layered on top of this. +This is a dummy package which brings in all subpackages. + +%package core +Summary: Core git tools +Group: Development/Tools +Requires: zlib >= 1.2, rsync, rcs, curl, less, openssh-clients, python >= 2.3, expat +%description core +This is a stupid (but extremely fast) directory content manager. It +doesn't do a whole lot, but what it _does_ do is track directory +contents efficiently. It is intended to be the base of an efficient, +distributed source code management system. This package includes +rudimentary tools that can be used as a SCM, but you should look +elsewhere for tools for ordinary humans layered on top of this. + +These are the core tools with minimal dependencies. + %package svn Summary: Git tools for importing Subversion repositories Group: Development/Tools @@ -47,11 +63,11 @@ Requires: git-core = %{version}-%{release} %description email Git tools for sending email. -%package tk +%package -n gitk Summary: Git revision tree visualiser ('gitk') Group: Development/Tools Requires: git-core = %{version}-%{release}, tk >= 8.4 -%description tk +%description -n gitk Git revision tree visualiser ('gitk') %prep @@ -75,6 +91,9 @@ make %{_smp_mflags} DESTDIR=$RPM_BUILD_ROOT WITH_OWN_SUBPROCESS_PY=YesPlease WIT %clean rm -rf $RPM_BUILD_ROOT +%files +# These are no files in the root package + %files svn %defattr(-,root,root) %{_bindir}/*svn* @@ -103,20 +122,25 @@ rm -rf $RPM_BUILD_ROOT %{!?_without_docs: %{_mandir}/man1/*email*.1*} %{!?_without_docs: %doc Documentation/*email*.html } -%files tk +%files -n gitk %defattr(-,root,root) %doc Documentation/*gitk*.txt %{_bindir}/*gitk* %{!?_without_docs: %{_mandir}/man1/*gitk*.1*} %{!?_without_docs: %doc Documentation/*gitk*.html } -%files -f bin-man-doc-files +%files core -f bin-man-doc-files %defattr(-,root,root) %{_datadir}/git-core/ %doc README COPYING Documentation/*.txt %{!?_without_docs: %doc Documentation/*.html } %changelog +* Mon Nov 14 2005 H. Peter Anvin 0.99.9j-1 +- Change subpackage names to git- instead of git-core- +- Create empty root package which brings in all subpackages +- Rename git-tk -> gitk + * Thu Nov 10 2005 Chris Wright 0.99.9g-1 - zlib dependency fix - Minor cleanups from split From bce8230d5d897a738cc004954df71f50a40295db Mon Sep 17 00:00:00 2001 From: Andreas Ericsson Date: Mon, 14 Nov 2005 17:41:01 +0100 Subject: [PATCH 02/32] git-daemon: --inetd implies --syslog Otherwise nothing is logged anywhere, which is a Bad Thing. Signed-off-by: Andreas Ericsson Signed-off-by: Junio C Hamano --- Documentation/git-daemon.txt | 2 +- daemon.c | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index 67c5f22a7d..3783858302 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -35,7 +35,7 @@ OPTIONS do not have the 'git-daemon-export-ok' file. --inetd:: - Have the server run as an inetd service. + Have the server run as an inetd service. Implies --syslog. --port:: Listen on an alternative port. diff --git a/daemon.c b/daemon.c index c3f86410d4..e184752298 100644 --- a/daemon.c +++ b/daemon.c @@ -628,8 +628,9 @@ int main(int argc, char **argv) if (inetd_mode) { fclose(stderr); //FIXME: workaround + log_syslog = 1; return execute(); - } else { - return serve(port); } + + return serve(port); } From 313c4714c5ec1673805b952ba79d910a42e8937c Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sat, 12 Nov 2005 00:55:16 +0100 Subject: [PATCH 03/32] Fix bunch of fd leaks in http-fetch The current http-fetch is rather careless about fd leakage, causing problems while fetching large repositories. This patch does not reserve exhaustiveness, but I covered everything I spotted. I also left some safeguards in place in case I missed something, so that we get to know, sooner or later. Reported by Becky Bruce . Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano --- http-fetch.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/http-fetch.c b/http-fetch.c index b8aa965ea3..21cc1b960c 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -425,6 +425,8 @@ static void start_request(struct transfer_request *request) rename(request->tmpfile, prevfile); unlink(request->tmpfile); + if (request->local != -1) + error("fd leakage in start: %d", request->local); request->local = open(request->tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0666); /* This could have failed due to the "lazy directory creation"; @@ -523,7 +525,7 @@ static void start_request(struct transfer_request *request) /* Try to get the request started, abort the request on error */ if (!start_active_slot(slot)) { request->state = ABORTED; - close(request->local); + close(request->local); request->local = -1; free(request->url); return; } @@ -537,7 +539,7 @@ static void finish_request(struct transfer_request *request) struct stat st; fchmod(request->local, 0444); - close(request->local); + close(request->local); request->local = -1; if (request->http_code == 416) { fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n"); @@ -569,6 +571,8 @@ static void release_request(struct transfer_request *request) { struct transfer_request *entry = request_queue_head; + if (request->local != -1) + error("fd leakage in release: %d", request->local); if (request == request_queue_head) { request_queue_head = request->next; } else { @@ -631,6 +635,8 @@ static void process_curl_messages(void) if (request->repo->next != NULL) { request->repo = request->repo->next; + close(request->local); + request->local = -1; start_request(request); } else { finish_request(request); @@ -763,6 +769,7 @@ static int fetch_index(struct alt_base *repo, unsigned char *sha1) curl_errorstr); } } else { + fclose(indexfile); return error("Unable to start request"); } @@ -1083,6 +1090,7 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1) curl_errorstr); } } else { + fclose(packfile); return error("Unable to start request"); } @@ -1145,6 +1153,7 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1) fetch_alternates(alt->base); if (request->repo->next != NULL) { request->repo = request->repo->next; + close(request->local); request->local = -1; start_request(request); } } else { @@ -1153,6 +1162,9 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1) } #endif } + if (request->local != -1) { + close(request->local); request->local = -1; + } if (request->state == ABORTED) { release_request(request); From cd0a781c386b197e63a30104bead39420eada7ca Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 15 Nov 2005 01:31:04 -0800 Subject: [PATCH 04/32] Documentation: do not blindly run 'cat' .git/HEAD, or echo into it. Many places in the documentation we still talked about reading what commit is recorded in .git/HEAD or writing the new head information into it, both assuming .git/HEAD is a symlink. That is not necessarily so. Signed-off-by: Junio C Hamano --- Documentation/diff-format.txt | 2 +- Documentation/git-commit-tree.txt | 5 +++-- Documentation/git-diff-index.txt | 6 +++--- Documentation/git-fsck-objects.txt | 2 +- Documentation/git-read-tree.txt | 2 +- Documentation/git-symbolic-ref.txt | 4 ++-- README | 6 +++--- 7 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt index b426a14f5e..97756ec030 100644 --- a/Documentation/diff-format.txt +++ b/Documentation/diff-format.txt @@ -81,7 +81,7 @@ The "diff" formatting options can be customized via the environment variable 'GIT_DIFF_OPTS'. For example, if you prefer context diff: - GIT_DIFF_OPTS=-c git-diff-index -p $(cat .git/HEAD) + GIT_DIFF_OPTS=-c git-diff-index -p HEAD 2. When the environment variable 'GIT_EXTERNAL_DIFF' is set, the diff --git a/Documentation/git-commit-tree.txt b/Documentation/git-commit-tree.txt index 5cf6bd3e21..a794192d7b 100644 --- a/Documentation/git-commit-tree.txt +++ b/Documentation/git-commit-tree.txt @@ -26,8 +26,9 @@ to get there. Normally a commit would identify a new "HEAD" state, and while git doesn't care where you save the note about that state, in practice we -tend to just write the result to the file `.git/HEAD`, so that we can -always see what the last committed state was. +tend to just write the result to the file that is pointed at by +`.git/HEAD`, so that we can always see what the last committed +state was. OPTIONS ------- diff --git a/Documentation/git-diff-index.txt b/Documentation/git-diff-index.txt index d8fc78fab9..dba6d30fcf 100644 --- a/Documentation/git-diff-index.txt +++ b/Documentation/git-diff-index.txt @@ -57,14 +57,14 @@ some files in the index and are ready to commit. You want to see eactly *what* you are going to commit is without having to write a new tree object and compare it that way, and to do that, you just do - git-diff-index --cached $(cat .git/HEAD) + git-diff-index --cached HEAD Example: let's say I had renamed `commit.c` to `git-commit.c`, and I had done an "git-update-index" to make that effective in the index file. "git-diff-files" wouldn't show anything at all, since the index file matches my working directory. But doing a "git-diff-index" does: - torvalds@ppc970:~/git> git-diff-index --cached $(cat .git/HEAD) + torvalds@ppc970:~/git> git-diff-index --cached HEAD -100644 blob 4161aecc6700a2eb579e842af0b7f22b98443f74 commit.c +100644 blob 4161aecc6700a2eb579e842af0b7f22b98443f74 git-commit.c @@ -98,7 +98,7 @@ show that. So let's say that you have edited `kernel/sched.c`, but have not actually done a "git-update-index" on it yet - there is no "object" associated with the new state, and you get: - torvalds@ppc970:~/v2.6/linux> git-diff-index $(cat .git/HEAD ) + torvalds@ppc970:~/v2.6/linux> git-diff-index HEAD *100644->100664 blob 7476bb......->000000...... kernel/sched.c ie it shows that the tree has changed, and that `kernel/sched.c` has is diff --git a/Documentation/git-fsck-objects.txt b/Documentation/git-fsck-objects.txt index 37e8055d21..bab1f6080c 100644 --- a/Documentation/git-fsck-objects.txt +++ b/Documentation/git-fsck-objects.txt @@ -68,7 +68,7 @@ that aren't readable from any of the specified head nodes. So for example - git-fsck-objects --unreachable $(cat .git/HEAD .git/refs/heads/*) + git-fsck-objects --unreachable HEAD $(cat .git/refs/heads/*) will do quite a _lot_ of verification on the tree. There are a few extra validity tests to be added (make sure that tree objects are diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 7be0cbd620..8b91847856 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -237,7 +237,7 @@ This is done to prevent you from losing your work-in-progress changes. To illustrate, suppose you start from what has been commited last to your repository: - $ JC=`cat .git/HEAD` + $ JC=`git-rev-parse --verify "HEAD^0"` $ git-checkout-index -f -u -a $JC You do random edits, without running git-update-index. And then diff --git a/Documentation/git-symbolic-ref.txt b/Documentation/git-symbolic-ref.txt index a851ae24c4..68ac6a65df 100644 --- a/Documentation/git-symbolic-ref.txt +++ b/Documentation/git-symbolic-ref.txt @@ -24,8 +24,8 @@ Traditionally, `.git/HEAD` is a symlink pointing at we did `ln -sf refs/heads/newbranch .git/HEAD`, and when we want to find out which branch we are on, we did `readlink .git/HEAD`. This was fine, and internally that is what still happens by -default, but on platforms that does not have working symlinks, -or that does not have the `readlink(1)` command, this was a bit +default, but on platforms that do not have working symlinks, +or that do not have the `readlink(1)` command, this was a bit cumbersome. On some platforms, `ln -sf` does not even work as advertised (horrors). diff --git a/README b/README index 4a2616ba57..36fef6ec04 100644 --- a/README +++ b/README @@ -396,8 +396,8 @@ git-commit-tree will return the name of the object that represents that commit, and you should save it away for later use. Normally, you'd commit a new `HEAD` state, and while git doesn't care where you save the note about that state, in practice we tend to just write the -result to the file `.git/HEAD`, so that we can always see what the -last committed state was. +result to the file pointed at by `.git/HEAD`, so that we can always see +what the last committed state was. Here is an ASCII art by Jon Loeliger that illustrates how various pieces fit together. @@ -464,7 +464,7 @@ tend to be small and fairly self-explanatory. In particular, if you follow the convention of having the top commit name in `.git/HEAD`, you can do - git-cat-file commit $(cat .git/HEAD) + git-cat-file commit HEAD to see what the top commit was. From a52e4ef877f693a69710145f56d867365a755f91 Mon Sep 17 00:00:00 2001 From: Jonas Fonseca Date: Tue, 15 Nov 2005 13:29:18 +0100 Subject: [PATCH 05/32] Fix git(1) link to git-index-pack Signed-off-by: Jonas Fonseca Signed-off-by: Junio C Hamano --- Documentation/git.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/git.txt b/Documentation/git.txt index 7045f3f97e..bd389bdb97 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -67,7 +67,7 @@ gitlink:git-commit-tree[1]:: gitlink:git-hash-object[1]:: Computes the object ID from a file. -gitlink:git-index-pack.html[1]:: +gitlink:git-index-pack[1]:: Build pack index file for an existing packed archive. gitlink:git-init-db[1]:: From 545f229a4b43212e683ac63e5aa740324ac7799e Mon Sep 17 00:00:00 2001 From: Sergey Vlasov Date: Tue, 15 Nov 2005 19:07:15 +0300 Subject: [PATCH 06/32] git-fsck-objects: Free tree entries after use The Massif tool of Valgrind revealed that parsed tree entries occupy more than 60% of memory allocated by git-fsck-objects. These entries can be freed immediately after use, which significantly decreases memory consumption. Signed-off-by: Sergey Vlasov Signed-off-by: Junio C Hamano --- fsck-objects.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fsck-objects.c b/fsck-objects.c index 17d05363e0..c1b279efcb 100644 --- a/fsck-objects.c +++ b/fsck-objects.c @@ -184,10 +184,17 @@ static int fsck_tree(struct tree *item) default: break; } + free(last->name); + free(last); } last = entry; } + if (last) { + free(last->name); + free(last); + } + item->entries = NULL; retval = 0; if (has_full_path) { From 4a4e6fd74f7d4564ed43eaaf59b6bd70c1959f1a Mon Sep 17 00:00:00 2001 From: Sergey Vlasov Date: Tue, 15 Nov 2005 19:08:08 +0300 Subject: [PATCH 07/32] Rework object refs tracking to reduce memory usage Store pointers to referenced objects in a variable sized array instead of linked list. This cuts down memory usage of utilities which use object references; e.g., git-fsck-objects --full on the git.git repository consumes about 2 MB of memory tracked by Massif instead of 7 MB before the change. Object refs are still the biggest consumer of memory (57%), but the malloc overhead for a single block instead of a linked list is substantially smaller. Signed-off-by: Sergey Vlasov Signed-off-by: Junio C Hamano --- commit.c | 19 +++++++++++++--- fsck-objects.c | 22 ++++++++++-------- object.c | 62 +++++++++++++++++++++++++++++++++++--------------- object.h | 10 ++++++-- server-info.c | 25 ++++++++++++++------ tag.c | 7 ++++-- tree.c | 13 ++++++++++- 7 files changed, 116 insertions(+), 42 deletions(-) diff --git a/commit.c b/commit.c index ebf4db6416..e867b86e6a 100644 --- a/commit.c +++ b/commit.c @@ -204,6 +204,7 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size) unsigned char parent[20]; struct commit_list **pptr; struct commit_graft *graft; + unsigned n_refs = 0; if (item->object.parsed) return 0; @@ -214,7 +215,7 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size) return error("bad tree pointer in commit %s\n", sha1_to_hex(item->object.sha1)); item->tree = lookup_tree(parent); if (item->tree) - add_ref(&item->object, &item->tree->object); + n_refs++; bufptr += 46; /* "tree " + "hex sha1" + "\n" */ pptr = &item->parents; @@ -230,7 +231,7 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size) new_parent = lookup_commit(parent); if (new_parent) { pptr = &commit_list_insert(new_parent, pptr)->next; - add_ref(&item->object, &new_parent->object); + n_refs++; } } if (graft) { @@ -241,10 +242,22 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size) if (!new_parent) continue; pptr = &commit_list_insert(new_parent, pptr)->next; - add_ref(&item->object, &new_parent->object); + n_refs++; } } item->date = parse_commit_date(bufptr); + + if (track_object_refs) { + unsigned i = 0; + struct commit_list *p; + struct object_refs *refs = alloc_object_refs(n_refs); + if (item->tree) + refs->ref[i++] = &item->tree->object; + for (p = item->parents; p; p = p->next) + refs->ref[i++] = &p->item->object; + set_object_refs(&item->object, refs); + } + return 0; } diff --git a/fsck-objects.c b/fsck-objects.c index c1b279efcb..0433a1d0da 100644 --- a/fsck-objects.c +++ b/fsck-objects.c @@ -56,7 +56,6 @@ static void check_connectivity(void) /* Look up all the requirements, warn about missing objects.. */ for (i = 0; i < nr_objs; i++) { struct object *obj = objs[i]; - struct object_list *refs; if (!obj->parsed) { if (!standalone && has_sha1_file(obj->sha1)) @@ -67,14 +66,19 @@ static void check_connectivity(void) continue; } - for (refs = obj->refs; refs; refs = refs->next) { - if (refs->item->parsed || - (!standalone && has_sha1_file(refs->item->sha1))) - continue; - printf("broken link from %7s %s\n", - obj->type, sha1_to_hex(obj->sha1)); - printf(" to %7s %s\n", - refs->item->type, sha1_to_hex(refs->item->sha1)); + if (obj->refs) { + const struct object_refs *refs = obj->refs; + unsigned j; + for (j = 0; j < refs->count; j++) { + struct object *ref = refs->ref[j]; + if (ref->parsed || + (!standalone && has_sha1_file(ref->sha1))) + continue; + printf("broken link from %7s %s\n", + obj->type, sha1_to_hex(obj->sha1)); + printf(" to %7s %s\n", + ref->type, sha1_to_hex(ref->sha1)); + } } if (show_unreachable && !(obj->flags & REACHABLE)) { diff --git a/object.c b/object.c index 1fdebe012b..427e14cae2 100644 --- a/object.c +++ b/object.c @@ -67,40 +67,66 @@ void created_object(const unsigned char *sha1, struct object *obj) nr_objs++; } -void add_ref(struct object *refer, struct object *target) +struct object_refs *alloc_object_refs(unsigned count) { - struct object_list **pp, *p; + struct object_refs *refs; + size_t size = sizeof(*refs) + count*sizeof(struct object *); - if (!track_object_refs) + refs = xmalloc(size); + memset(refs, 0, size); + refs->count = count; + return refs; +} + +static int compare_object_pointers(const void *a, const void *b) +{ + const struct object * const *pa = a; + const struct object * const *pb = b; + return *pa - *pb; +} + +void set_object_refs(struct object *obj, struct object_refs *refs) +{ + unsigned int i, j; + + /* Do not install empty list of references */ + if (refs->count < 1) { + free(refs); return; + } - pp = &refer->refs; - while ((p = *pp) != NULL) { - if (p->item == target) - return; - pp = &p->next; + /* Sort the list and filter out duplicates */ + qsort(refs->ref, refs->count, sizeof(refs->ref[0]), + compare_object_pointers); + for (i = j = 1; i < refs->count; i++) { + if (refs->ref[i] != refs->ref[i - 1]) + refs->ref[j++] = refs->ref[i]; + } + if (j < refs->count) { + /* Duplicates were found - reallocate list */ + size_t size = sizeof(*refs) + j*sizeof(struct object *); + refs->count = j; + refs = xrealloc(refs, size); } - target->used = 1; - p = xmalloc(sizeof(*p)); - p->item = target; - p->next = NULL; - *pp = p; + for (i = 0; i < refs->count; i++) + refs->ref[i]->used = 1; + obj->refs = refs; } void mark_reachable(struct object *obj, unsigned int mask) { - struct object_list *p = obj->refs; - if (!track_object_refs) die("cannot do reachability with object refs turned off"); /* If we've been here already, don't bother */ if (obj->flags & mask) return; obj->flags |= mask; - while (p) { - mark_reachable(p->item, mask); - p = p->next; + if (obj->refs) { + const struct object_refs *refs = obj->refs; + unsigned i; + for (i = 0; i < refs->count; i++) + mark_reachable(refs->ref[i], mask); } } diff --git a/object.h b/object.h index 6accda33d8..336d986b51 100644 --- a/object.h +++ b/object.h @@ -7,13 +7,18 @@ struct object_list { const char *name; }; +struct object_refs { + unsigned count; + struct object *ref[0]; +}; + struct object { unsigned parsed : 1; unsigned used : 1; unsigned int flags; unsigned char sha1[20]; const char *type; - struct object_list *refs; + struct object_refs *refs; void *util; }; @@ -35,7 +40,8 @@ struct object *parse_object(const unsigned char *sha1); /** Returns the object, with potentially excess memory allocated. **/ struct object *lookup_unknown_object(const unsigned char *sha1); -void add_ref(struct object *refer, struct object *target); +struct object_refs *alloc_object_refs(unsigned count); +void set_object_refs(struct object *obj, struct object_refs *refs); void mark_reachable(struct object *obj, unsigned int mask); diff --git a/server-info.c b/server-info.c index 0cba8e19f7..e4006f0b5b 100644 --- a/server-info.c +++ b/server-info.c @@ -424,7 +424,6 @@ static void find_pack_info_one(int pack_ix) { unsigned char sha1[20]; struct object *o; - struct object_list *ref; int i; struct packed_git *p = info[pack_ix]->p; int num = num_packed_objects(p); @@ -437,8 +436,12 @@ static void find_pack_info_one(int pack_ix) die("corrupt pack file %s?", p->pack_name); if ((o = lookup_object(sha1)) == NULL) die("cannot parse %s", sha1_to_hex(sha1)); - for (ref = o->refs; ref; ref = ref->next) - ref->item->flags = 0; + if (o->refs) { + struct object_refs *refs = o->refs; + int j; + for (j = 0; j < refs->count; j++) + refs->ref[j]->flags = 0; + } o->flags = 0; } @@ -448,8 +451,12 @@ static void find_pack_info_one(int pack_ix) die("corrupt pack file %s?", p->pack_name); if ((o = lookup_object(sha1)) == NULL) die("cannot find %s", sha1_to_hex(sha1)); - for (ref = o->refs; ref; ref = ref->next) - ref->item->flags |= REFERENCED; + if (o->refs) { + struct object_refs *refs = o->refs; + int j; + for (j = 0; j < refs->count; j++) + refs->ref[j]->flags |= REFERENCED; + } o->flags |= INTERNAL; } @@ -460,8 +467,12 @@ static void find_pack_info_one(int pack_ix) die("cannot find %s", sha1_to_hex(sha1)); show(o, pack_ix); - for (ref = o->refs; ref; ref = ref->next) - show(ref->item, pack_ix); + if (o->refs) { + struct object_refs *refs = o->refs; + int j; + for (j = 0; j < refs->count; j++) + show(refs->ref[j], pack_ix); + } } } diff --git a/tag.c b/tag.c index e574c4b7a4..61ac434d6b 100644 --- a/tag.c +++ b/tag.c @@ -75,8 +75,11 @@ int parse_tag_buffer(struct tag *item, void *data, unsigned long size) item->tag[taglen] = '\0'; item->tagged = lookup_object_type(object, type); - if (item->tagged) - add_ref(&item->object, item->tagged); + if (item->tagged && track_object_refs) { + struct object_refs *refs = alloc_object_refs(1); + refs->ref[0] = item->tagged; + set_object_refs(&item->object, refs); + } return 0; } diff --git a/tree.c b/tree.c index 315b6a5d1c..8b42a07b20 100644 --- a/tree.c +++ b/tree.c @@ -148,6 +148,7 @@ int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size) { void *bufptr = buffer; struct tree_entry_list **list_p; + int n_refs = 0; if (item->object.parsed) return 0; @@ -184,11 +185,21 @@ int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size) obj = &entry->item.blob->object; } if (obj) - add_ref(&item->object, obj); + n_refs++; entry->parent = NULL; /* needs to be filled by the user */ *list_p = entry; list_p = &entry->next; } + + 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); + } + return 0; } From f8348be3be8493a62110a09ab0343213990b416b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 15 Nov 2005 19:24:19 +0100 Subject: [PATCH 08/32] Add config variable core.symrefsonly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows you to force git to avoid symlinks for refs. Just add something like [core] symrefsonly = true to .git/config. Don´t forget to "git checkout your_branch", or it does not do anything... Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- cache.h | 1 + config.c | 5 +++++ environment.c | 1 + refs.c | 10 ++++++---- symbolic-ref.c | 1 + 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/cache.h b/cache.h index 677c6acc35..9a6bfb96d9 100644 --- a/cache.h +++ b/cache.h @@ -179,6 +179,7 @@ extern int commit_index_file(struct cache_file *); extern void rollback_index_file(struct cache_file *); extern int trust_executable_bit; +extern int only_use_symrefs; #define MTIME_CHANGED 0x0001 #define CTIME_CHANGED 0x0002 diff --git a/config.c b/config.c index e89bab26c9..bd35138dae 100644 --- a/config.c +++ b/config.c @@ -214,6 +214,11 @@ int git_default_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.symrefsonly")) { + only_use_symrefs = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "user.name")) { strncpy(git_default_name, value, sizeof(git_default_name)); return 0; diff --git a/environment.c b/environment.c index 1dc7af56cf..b5026f1265 100644 --- a/environment.c +++ b/environment.c @@ -12,6 +12,7 @@ char git_default_email[MAX_GITNAME]; char git_default_name[MAX_GITNAME]; int trust_executable_bit = 1; +int only_use_symrefs = 0; static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file; diff --git a/refs.c b/refs.c index a52b038eef..f324be5032 100644 --- a/refs.c +++ b/refs.c @@ -121,10 +121,12 @@ int create_symref(const char *git_HEAD, const char *refs_heads_master) int fd, len, written; #if USE_SYMLINK_HEAD - unlink(git_HEAD); - if (!symlink(refs_heads_master, git_HEAD)) - return 0; - fprintf(stderr, "no symlink - falling back to symbolic ref\n"); + if (!only_use_symrefs) { + unlink(git_HEAD); + if (!symlink(refs_heads_master, git_HEAD)) + return 0; + fprintf(stderr, "no symlink - falling back to symbolic ref\n"); + } #endif len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master); diff --git a/symbolic-ref.c b/symbolic-ref.c index a72d7accb1..193c87c174 100644 --- a/symbolic-ref.c +++ b/symbolic-ref.c @@ -20,6 +20,7 @@ static void check_symref(const char *HEAD) int main(int argc, const char **argv) { setup_git_directory(); + git_config(git_default_config); switch (argc) { case 2: check_symref(argv[1]); From 3299c6f6a8a384453d025ffa117c5d8b35ba1972 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 15 Nov 2005 12:48:08 -0800 Subject: [PATCH 09/32] diff: make default rename detection limit configurable. A while ago, a rename-detection limit logic was implemented as a response to this thread: http://marc.theaimsgroup.com/?l=git&m=112413080630175 where gitweb was found to be using a lot of time and memory to detect renames on huge commits. git-diff family takes -l flag, and if the number of paths that are rename destination candidates (i.e. new paths with -M, or modified paths with -C) are larger than that number, skips rename/copy detection even when -M or -C is specified on the command line. This commit makes the rename detection limit easier to use. You can have: [diff] renamelimit = 30 in your .git/config file to specify the default rename detection limit. You can override this from the command line; giving 0 means 'unlimited': git diff -M -l0 We might want to change the default behaviour, when you do not have the configuration, to limit it to say 20 paths or so. This would also help the diffstat generation after a big 'git pull'. Signed-off-by: Junio C Hamano --- Makefile | 4 ++-- cache.h | 1 + config.c | 5 +++++ diff.c | 9 +++++++-- diffcore-rename.c | 2 +- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index b10a31550a..21bc3d384e 100644 --- a/Makefile +++ b/Makefile @@ -105,7 +105,7 @@ SCRIPT_PYTHON = \ # The ones that do not have to link with lcrypto nor lz. SIMPLE_PROGRAMS = \ git-get-tar-commit-id$X git-mailinfo$X git-mailsplit$X \ - git-stripspace$X git-var$X git-daemon$X + git-stripspace$X git-daemon$X # ... and all the rest PROGRAMS = \ @@ -125,7 +125,7 @@ PROGRAMS = \ git-unpack-objects$X git-update-index$X git-update-server-info$X \ git-upload-pack$X git-verify-pack$X git-write-tree$X \ git-update-ref$X git-symbolic-ref$X git-check-ref-format$X \ - git-name-rev$X git-pack-redundant$X $(SIMPLE_PROGRAMS) + git-name-rev$X git-pack-redundant$X git-var$X $(SIMPLE_PROGRAMS) # Backward compatibility -- to be removed after 1.0 PROGRAMS += git-ssh-pull$X git-ssh-push$X diff --git a/cache.h b/cache.h index 9a6bfb96d9..08461cfc5a 100644 --- a/cache.h +++ b/cache.h @@ -180,6 +180,7 @@ extern void rollback_index_file(struct cache_file *); extern int trust_executable_bit; extern int only_use_symrefs; +extern int diff_rename_limit_default; #define MTIME_CHANGED 0x0001 #define CTIME_CHANGED 0x0002 diff --git a/config.c b/config.c index bd35138dae..915bb97523 100644 --- a/config.c +++ b/config.c @@ -229,6 +229,11 @@ int git_default_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "diff.renamelimit")) { + diff_rename_limit_default = git_config_int(var, value); + return 0; + } + /* Add other config variables here.. */ return 0; } diff --git a/diff.c b/diff.c index ec94a96a5d..fca61f32f0 100644 --- a/diff.c +++ b/diff.c @@ -13,6 +13,8 @@ static const char *diff_opts = "-pu"; static int use_size_cache; +int diff_rename_limit_default = -1; + static char *quote_one(const char *str) { int needlen; @@ -761,9 +763,12 @@ void diff_setup(struct diff_options *options) int diff_setup_done(struct diff_options *options) { - if ((options->find_copies_harder || 0 <= options->rename_limit) && - options->detect_rename != DIFF_DETECT_COPY) + if ((options->find_copies_harder && + options->detect_rename != DIFF_DETECT_COPY) || + (0 <= options->rename_limit && !options->detect_rename)) return -1; + if (options->detect_rename && options->rename_limit < 0) + options->rename_limit = diff_rename_limit_default; if (options->setup & DIFF_SETUP_USE_CACHE) { if (!active_cache) /* read-cache does not die even when it fails diff --git a/diffcore-rename.c b/diffcore-rename.c index e17dd90058..6a9d95d059 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -283,7 +283,7 @@ void diffcore_rename(struct diff_options *options) register_rename_src(p->one, 1); } if (rename_dst_nr == 0 || - (0 <= rename_limit && rename_limit < rename_dst_nr)) + (0 < rename_limit && rename_limit < rename_dst_nr)) goto cleanup; /* nothing to do */ /* We really want to cull the candidates list early From c0bbbb1ba93154e31f05eab69fee70006fe7acb2 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 15 Nov 2005 12:51:02 -0800 Subject: [PATCH 10/32] sha1_file.c::add_packed_git(): fix type mismatch. An object name is 20-byte 'unsigned char', not 'char'. Signed-off-by: Junio C Hamano --- sha1_file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sha1_file.c b/sha1_file.c index cd814d7233..82a01887c2 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -424,7 +424,7 @@ struct packed_git *add_packed_git(char *path, int path_len, int local) struct packed_git *p; unsigned long idx_size; void *idx_map; - char sha1[20]; + unsigned char sha1[20]; if (check_packed_git_idx(path, &idx_size, &idx_map)) return NULL; From 8e49d50388211a0f3e7286f6ee600bf7736f4814 Mon Sep 17 00:00:00 2001 From: Andreas Ericsson Date: Wed, 16 Nov 2005 00:31:25 +0100 Subject: [PATCH 11/32] C implementation of the 'git' program, take two. This patch provides a C implementation of the 'git' program and introduces support for putting the git-* commands in a directory of their own. It also saves some time on executing those commands in a tight loop and it prints the currently available git commands in a nicely formatted list. The location of the GIT_EXEC_PATH (name discussion's closed, thank gods) can be obtained by running git --exec-path which will hopefully give porcelainistas ample time to adapt their heavy-duty loops to call the core programs directly and thus save the extra fork() / execve() overhead, although that's not really necessary any more. The --exec-path value is prepended to $PATH, so the git-* programs should Just Work without ever requiring any changes to how they call other programs in the suite. Some timing values for 10000 invocations of git-var >&/dev/null: git.sh: 24.194s git.c: 9.044s git-var: 7.377s The git- behaviour can, along with the someday-to-be-deprecated git- form of invocation, be indefinitely retained by adding the following line to one's .bash_profile or equivalent: PATH=$PATH:$(git --exec-path) Experimental libraries can be used by either setting the environment variable GIT_EXEC_PATH, or by using git --exec-path=/some/experimental/exec-path Relative paths are properly grok'ed as exec-path values. Signed-off-by: Andreas Ericsson Signed-off-by: Junio C Hamano --- Makefile | 20 ++--- git.c | 229 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ git.sh | 76 ------------------ 3 files changed, 237 insertions(+), 88 deletions(-) create mode 100644 git.c delete mode 100755 git.sh diff --git a/Makefile b/Makefile index 21bc3d384e..ebff990b58 100644 --- a/Makefile +++ b/Makefile @@ -88,7 +88,7 @@ SCRIPT_SH = \ git-prune.sh git-pull.sh git-push.sh git-rebase.sh \ git-repack.sh git-request-pull.sh git-reset.sh \ git-resolve.sh git-revert.sh git-sh-setup.sh git-status.sh \ - git-tag.sh git-verify-tag.sh git-whatchanged.sh git.sh \ + git-tag.sh git-verify-tag.sh git-whatchanged.sh \ git-applymbox.sh git-applypatch.sh git-am.sh \ git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \ git-merge-resolve.sh git-merge-ours.sh git-grep.sh \ @@ -334,19 +334,15 @@ SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir ### Build rules -all: $(PROGRAMS) $(SCRIPTS) +all: $(PROGRAMS) $(SCRIPTS) git all: $(MAKE) -C templates -git: git.sh Makefile - rm -f $@+ $@ - sed -e '1s|#!.*/sh|#!$(call shq,$(SHELL_PATH))|' \ - -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ - -e 's/@@X@@/$(X)/g' \ - $(GIT_LIST_TWEAK) <$@.sh >$@+ - chmod +x $@+ - mv $@+ $@ +# Only use $(CFLAGS). We don't need anything else. +git: git.c Makefile + $(CC) -DGIT_EXEC_PATH='"$(bindir)"' -DGIT_VERSION='"$(GIT_VERSION)"' \ + $(CFLAGS) $@.c -o $@ $(filter-out git,$(patsubst %.sh,%,$(SCRIPT_SH))) : % : %.sh rm -f $@ @@ -431,9 +427,9 @@ check: ### Installation rules -install: $(PROGRAMS) $(SCRIPTS) +install: $(PROGRAMS) $(SCRIPTS) git $(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(bindir)) - $(INSTALL) $(PROGRAMS) $(SCRIPTS) $(call shellquote,$(DESTDIR)$(bindir)) + $(INSTALL) git $(PROGRAMS) $(SCRIPTS) $(call shellquote,$(DESTDIR)$(bindir)) $(MAKE) -C templates install $(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(GIT_PYTHON_DIR)) $(INSTALL) $(PYMODULES) $(call shellquote,$(DESTDIR)$(GIT_PYTHON_DIR)) diff --git a/git.c b/git.c new file mode 100644 index 0000000000..d18980135f --- /dev/null +++ b/git.c @@ -0,0 +1,229 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef PATH_MAX +# define PATH_MAX 4096 +#endif + +static const char git_usage[] = + "Usage: git [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ ARGS ]"; + +struct string_list { + size_t len; + char *str; + struct string_list *next; +}; + +/* most gui terms set COLUMNS (although some don't export it) */ +static int term_columns(void) +{ + char *col_string = getenv("COLUMNS"); + int n_cols = 0; + + if (col_string && (n_cols = atoi(col_string)) > 0) + return n_cols; + + return 80; +} + +static inline void mput_char(char c, unsigned int num) +{ + while(num--) + putchar(c); +} + +static void pretty_print_string_list(struct string_list *list, int longest) +{ + int cols = 1; + int space = longest + 1; /* min 1 SP between words */ + int max_cols = term_columns() - 1; /* don't print *on* the edge */ + + if (space < max_cols) + cols = max_cols / space; + + while (list) { + int c; + printf(" "); + + for (c = cols; c && list; list = list->next) { + printf("%s", list->str); + + if (--c) + mput_char(' ', space - list->len); + } + putchar('\n'); + } +} + +static void list_commands(const char *exec_path, const char *pattern) +{ + struct string_list *list = NULL, *tail = NULL; + unsigned int longest = 0, i; + glob_t gl; + + if (chdir(exec_path) < 0) { + printf("git: '%s': %s\n", exec_path, strerror(errno)); + exit(1); + } + + i = glob(pattern, 0, NULL, &gl); + switch(i) { + case GLOB_NOSPACE: + puts("Out of memory when running glob()"); + exit(2); + case GLOB_ABORTED: + printf("'%s': Read error: %s\n", exec_path, strerror(errno)); + exit(2); + case GLOB_NOMATCH: + printf("No git commands available in '%s'.\n", exec_path); + printf("Do you need to specify --exec-path or set GIT_EXEC_PATH?\n"); + exit(1); + } + + for (i = 0; i < gl.gl_pathc; i++) { + int len = strlen(gl.gl_pathv[i] + 4); + + if (access(gl.gl_pathv[i], X_OK)) + continue; + + if (longest < len) + longest = len; + + if (!tail) + tail = list = malloc(sizeof(struct string_list)); + else { + tail->next = malloc(sizeof(struct string_list)); + tail = tail->next; + } + tail->len = len; + tail->str = gl.gl_pathv[i] + 4; + tail->next = NULL; + } + + printf("git commands available in '%s'\n", exec_path); + printf("----------------------------"); + mput_char('-', strlen(exec_path)); + putchar('\n'); + pretty_print_string_list(list, longest); + putchar('\n'); +} + +#ifdef __GNUC__ +static void usage(const char *exec_path, const char *fmt, ...) + __attribute__((__format__(__printf__, 2, 3), __noreturn__)); +#endif +static void usage(const char *exec_path, const char *fmt, ...) +{ + if (fmt) { + va_list ap; + + va_start(ap, fmt); + printf("git: "); + vprintf(fmt, ap); + va_end(ap); + putchar('\n'); + } + else + puts(git_usage); + + putchar('\n'); + + if(exec_path) + list_commands(exec_path, "git-*"); + + exit(1); +} + +static void prepend_to_path(const char *dir, int len) +{ + char *path, *old_path = getenv("PATH"); + int path_len = len; + + if (!old_path) + old_path = "/bin:/usr/bin:."; + + path_len = len + strlen(old_path) + 1; + + path = malloc(path_len + 1); + path[path_len + 1] = '\0'; + + memcpy(path, dir, len); + path[len] = ':'; + memcpy(path + len + 1, old_path, path_len - len); + + setenv("PATH", path, 1); +} + +int main(int argc, char **argv, char **envp) +{ + char git_command[PATH_MAX + 1]; + char wd[PATH_MAX + 1]; + int i, len, show_help = 0; + char *exec_path = getenv("GIT_EXEC_PATH"); + + getcwd(wd, PATH_MAX); + + if (!exec_path) + exec_path = GIT_EXEC_PATH; + + for (i = 1; i < argc; i++) { + char *arg = argv[i]; + + if (strncmp(arg, "--", 2)) + break; + + arg += 2; + + if (!strncmp(arg, "exec-path", 9)) { + arg += 9; + if (*arg == '=') + exec_path = arg + 1; + else { + puts(exec_path); + exit(0); + } + } + else if (!strcmp(arg, "version")) { + printf("git version %s\n", GIT_VERSION); + exit(0); + } + else if (!strcmp(arg, "help")) + show_help = 1; + else if (!show_help) + usage(NULL, NULL); + } + + if (i >= argc || show_help) + usage(exec_path, NULL); + + /* allow relative paths, but run with exact */ + if (chdir(exec_path)) { + printf("git: '%s': %s\n", exec_path, strerror(errno)); + exit (1); + } + + getcwd(git_command, sizeof(git_command)); + chdir(wd); + + len = strlen(git_command); + prepend_to_path(git_command, len); + + strncat(&git_command[len], "/git-", sizeof(git_command) - len); + len += 5; + strncat(&git_command[len], argv[i], sizeof(git_command) - len); + + if (access(git_command, X_OK)) + usage(exec_path, "'%s' is not a git-command", argv[i]); + + /* execve() can only ever return if it fails */ + execve(git_command, &argv[i], envp); + printf("Failed to run command '%s': %s\n", git_command, strerror(errno)); + + return 1; +} diff --git a/git.sh b/git.sh deleted file mode 100755 index 94940aea28..0000000000 --- a/git.sh +++ /dev/null @@ -1,76 +0,0 @@ -#!/bin/sh - -cmd= -path=$(dirname "$0") -case "$#" in -0) ;; -*) cmd="$1" - shift - case "$cmd" in - -v|--v|--ve|--ver|--vers|--versi|--versio|--version) - echo "git version @@GIT_VERSION@@" - exit 0 ;; - esac - - test -x "$path/git-$cmd" && exec "$path/git-$cmd" "$@" - - case '@@X@@' in - '') - ;; - *) - test -x "$path/git-$cmd@@X@@" && - exec "$path/git-$cmd@@X@@" "$@" - ;; - esac - ;; -esac - -echo "Usage: git COMMAND [OPTIONS] [TARGET]" -if [ -n "$cmd" ]; then - echo "git command '$cmd' not found." -fi -echo "git commands are:" - -fmt <<\EOF | sed -e 's/^/ /' -add -apply -archimport -bisect -branch -checkout -cherry -clone -commit -count-objects -cvsimport -diff -fetch -format-patch -fsck-objects -get-tar-commit-id -init-db -log -ls-remote -octopus -pack-objects -parse-remote -patch-id -prune -pull -push -rebase -relink -rename -repack -request-pull -reset -resolve -revert -send-email -shortlog -show-branch -status -tag -verify-tag -whatchanged -EOF From cb22bc44474686f1e0d1ad991732ccd634498729 Mon Sep 17 00:00:00 2001 From: Andreas Ericsson Date: Wed, 16 Nov 2005 00:31:25 +0100 Subject: [PATCH 12/32] Update git(7) man-page for the C wrapper. The program 'git' now has --exec-path which needs explaining. Renamed old "DESCRIPTION" to "CORE GIT COMMANDS" to make room for "OPTIONS" while following follow some sort of convention. Also updated AUTHORS section to pat my own back a bit. Signed-off-by: Andreas Ericsson Signed-off-by: Junio C Hamano --- Documentation/git.txt | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/Documentation/git.txt b/Documentation/git.txt index bd389bdb97..15ae4f12d0 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -8,13 +8,31 @@ git - the stupid content tracker SYNOPSIS -------- -'git-' +'git' [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ARGS] DESCRIPTION ----------- - -This is reference information for the core git commands. - +'git' is both a program and a directory content tracker system. +The program 'git' is just a wrapper to reach the core git programs +(or a potty if you like, as it's not exactly porcelain but still +brings your stuff to the plumbing). + +OPTIONS +------- +--version:: + prints the git suite version that the 'git' program came from. + +--help:: + prints the synopsis and a list of available commands. + +--exec-path:: + path to wherever your core git programs are installed. + This can also be controlled by setting the GIT_EXEC_PATH + environment variable. If no path is given 'git' will print + the current setting and then exit. + +CORE GIT COMMANDS +----------------- Before reading this cover to cover, you may want to take a look at the link:tutorial.html[tutorial] document. @@ -533,9 +551,12 @@ Discussion[[Discussion]] ------------------------ include::../README[] -Author ------- -Written by Linus Torvalds and the git-list . +Authors +------- + git's founding father is Linus Torvalds . + The current git nurse is Junio C. Hamano . + The git potty was written by Andres Ericsson . + General upbringing is handled by the git-list . Documentation -------------- From 97fc6c5fbacc2181319bbd7e2faa8ac04476f862 Mon Sep 17 00:00:00 2001 From: Andreas Ericsson Date: Wed, 16 Nov 2005 00:31:25 +0100 Subject: [PATCH 13/32] git --help COMMAND brings up the git-COMMAND man-page. It's by design a bit stupid (matching ^git rather than ^git-), so as to work with 'gitk' and 'git' as well. Signed-off-by: Andreas Ericsson Signed-off-by: Junio C Hamano --- Documentation/git.txt | 2 ++ git.c | 28 ++++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/Documentation/git.txt b/Documentation/git.txt index 15ae4f12d0..338e5acb8b 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -24,6 +24,8 @@ OPTIONS --help:: prints the synopsis and a list of available commands. + If a git command is named this option will bring up the + man-page for that command. --exec-path:: path to wherever your core git programs are installed. diff --git a/git.c b/git.c index d18980135f..583923d960 100644 --- a/git.c +++ b/git.c @@ -160,6 +160,26 @@ static void prepend_to_path(const char *dir, int len) setenv("PATH", path, 1); } +/* has anyone seen 'man' installed anywhere else than in /usr/bin? */ +#define PATH_TO_MAN "/usr/bin/man" +static void show_man_page(char *git_cmd) +{ + char *page; + + if (!strncmp(git_cmd, "git", 3)) + page = git_cmd; + else { + int page_len = strlen(git_cmd) + 4; + + page = malloc(page_len + 1); + strcpy(page, "git-"); + strcpy(page + 4, git_cmd); + page[page_len] = 0; + } + + execlp(PATH_TO_MAN, "man", page, NULL); +} + int main(int argc, char **argv, char **envp) { char git_command[PATH_MAX + 1]; @@ -199,8 +219,12 @@ int main(int argc, char **argv, char **envp) usage(NULL, NULL); } - if (i >= argc || show_help) - usage(exec_path, NULL); + if (i >= argc || show_help) { + if (i >= argc) + usage(exec_path, NULL); + + show_man_page(argv[i]); + } /* allow relative paths, but run with exact */ if (chdir(exec_path)) { From 1a41e743c6270a24daca7309ef3d9ef74543d8ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Sandstr=C3=B6m?= Date: Tue, 15 Nov 2005 22:24:02 +0100 Subject: [PATCH 14/32] Fix llist_sorted_difference_inplace in git-pack-redundant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simplify and actually make llist_sorted_difference_inplace work by using llist_sorted_remove instead of duplicating parts of the code. Signed-off-by: Lukas Sandström Signed-off-by: Junio C Hamano --- pack-redundant.c | 47 +++++++++++++++-------------------------------- 1 file changed, 15 insertions(+), 32 deletions(-) diff --git a/pack-redundant.c b/pack-redundant.c index 28b82ee65a..fcb36ff901 100644 --- a/pack-redundant.c +++ b/pack-redundant.c @@ -127,38 +127,6 @@ inline struct llist_item * llist_insert_sorted_unique(struct llist *list, return llist_insert_back(list, sha1); } -/* computes A\B */ -void llist_sorted_difference_inplace(struct llist *A, - struct llist *B) -{ - struct llist_item *prev, *a, *b, *x; - - prev = a = A->front; - b = B->front; - - while (a != NULL && b != NULL) { - int cmp = memcmp(a->sha1, b->sha1, 20); - if (!cmp) { - x = a; - if (a == A->front) - A->front = a->next; - a = prev->next = a->next; - - if (a == NULL) /* end of list */ - A->back = prev; - A->size--; - free(x); - b = b->next; - } else - if (cmp > 0) - b = b->next; - else { - prev = a; - a = a->next; - } - } -} - /* returns a pointer to an item in front of sha1 */ inline struct llist_item * llist_sorted_remove(struct llist *list, char *sha1, struct llist_item *hint) @@ -194,6 +162,21 @@ redo_from_start: return prev; } +/* computes A\B */ +void llist_sorted_difference_inplace(struct llist *A, + struct llist *B) +{ + struct llist_item *hint, *b; + + hint = NULL; + b = B->front; + + while (b) { + hint = llist_sorted_remove(A, b->sha1, hint); + b = b->next; + } +} + inline struct pack_list * pack_list_insert(struct pack_list **pl, struct pack_list *entry) { From a0fa2a10b401aa4c8b13d176a5e3e3b7c455208f Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 16 Nov 2005 02:44:50 +0100 Subject: [PATCH 15/32] Fix tests with new git in C GIT_EXEC_PATH *has* to be set. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- t/test-lib.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/t/test-lib.sh b/t/test-lib.sh index a8f239df8f..e654155a2e 100755 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -158,6 +158,8 @@ test_done () { # Test the binaries we have just built. The tests are kept in # t/ subdirectory and are run in trash subdirectory. PATH=$(pwd)/..:$PATH +GIT_EXEC_PATH=$(pwd)/.. +export GIT_EXEC_PATH # Test repository test=trash From ad4f4daae80cb00000aca76e1528add6daf8f033 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 16 Nov 2005 03:33:44 +0100 Subject: [PATCH 16/32] Give python a chance to find "backported" modules python 2.2.1 is perfectly capable of executing git-merge-recursive, provided that it finds heapq and sets. All you have to do is to steal heapq.py and sets.py from python 2.3 or newer, and drop them in your GIT_PYTHON_PATH. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- git-merge-recursive.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/git-merge-recursive.py b/git-merge-recursive.py index 1bf73f336d..d7d36aa7d1 100755 --- a/git-merge-recursive.py +++ b/git-merge-recursive.py @@ -3,11 +3,13 @@ # Copyright (C) 2005 Fredrik Kuivinen # -import sys, math, random, os, re, signal, tempfile, stat, errno, traceback +import sys +sys.path.append('''@@GIT_PYTHON_PATH@@''') + +import math, random, os, re, signal, tempfile, stat, errno, traceback from heapq import heappush, heappop from sets import Set -sys.path.append('''@@GIT_PYTHON_PATH@@''') from gitMergeCommon import * outputIndent = 0 From 7dbc2c0402d728a206d4f1bc59729bf3a5cc4455 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 15 Nov 2005 23:13:30 -0800 Subject: [PATCH 17/32] git wrapper: basic fixes. Updates to fix the nits found during the list discussion. - Lose PATH_TO_MAN; just rely on execlp() to find whereever the "man" command is installed. - Do not randomly chdir(), but concatenate to the current working directory only if the given path is not absolute. - Lose use of glob(); read from exec_path and do sorting ourselves -- it is not that much more work. Signed-off-by: Junio C Hamano --- git.c | 154 +++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 98 insertions(+), 56 deletions(-) diff --git a/git.c b/git.c index 583923d960..b9b8c62f47 100644 --- a/git.c +++ b/git.c @@ -1,11 +1,13 @@ #include +#include +#include +#include #include #include #include #include #include #include -#include #ifndef PATH_MAX # define PATH_MAX 4096 @@ -14,12 +16,6 @@ static const char git_usage[] = "Usage: git [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ ARGS ]"; -struct string_list { - size_t len; - char *str; - struct string_list *next; -}; - /* most gui terms set COLUMNS (although some don't export it) */ static int term_columns(void) { @@ -32,30 +28,69 @@ static int term_columns(void) return 80; } +static void oom(void) +{ + fprintf(stderr, "git: out of memory\n"); + exit(1); +} + static inline void mput_char(char c, unsigned int num) { while(num--) putchar(c); } -static void pretty_print_string_list(struct string_list *list, int longest) +static struct cmdname { + size_t len; + char name[1]; +} **cmdname; +static int cmdname_alloc, cmdname_cnt; + +static void add_cmdname(const char *name, int len) +{ + struct cmdname *ent; + if (cmdname_alloc <= cmdname_cnt) { + cmdname_alloc = cmdname_alloc + 200; + cmdname = realloc(cmdname, cmdname_alloc * sizeof(*cmdname)); + if (!cmdname) + oom(); + } + ent = malloc(sizeof(*ent) + len); + if (!ent) + oom(); + ent->len = len; + memcpy(ent->name, name, len+1); + cmdname[cmdname_cnt++] = ent; +} + +static int cmdname_compare(const void *a_, const void *b_) +{ + struct cmdname *a = *(struct cmdname **)a_; + struct cmdname *b = *(struct cmdname **)b_; + return strcmp(a->name, b->name); +} + +static void pretty_print_string_list(struct cmdname **cmdname, int longest) { int cols = 1; int space = longest + 1; /* min 1 SP between words */ int max_cols = term_columns() - 1; /* don't print *on* the edge */ + int i; if (space < max_cols) cols = max_cols / space; - while (list) { + qsort(cmdname, cmdname_cnt, sizeof(*cmdname), cmdname_compare); + + for (i = 0; i < cmdname_cnt; ) { int c; printf(" "); - for (c = cols; c && list; list = list->next) { - printf("%s", list->str); + for (c = cols; c && i < cmdname_cnt; i++) { + printf("%s", cmdname[i]->name); if (--c) - mput_char(' ', space - list->len); + mput_char(' ', space - cmdname[i]->len); } putchar('\n'); } @@ -63,54 +98,53 @@ static void pretty_print_string_list(struct string_list *list, int longest) static void list_commands(const char *exec_path, const char *pattern) { - struct string_list *list = NULL, *tail = NULL; - unsigned int longest = 0, i; - glob_t gl; - - if (chdir(exec_path) < 0) { - printf("git: '%s': %s\n", exec_path, strerror(errno)); + unsigned int longest = 0; + char path[PATH_MAX]; + int dirlen; + DIR *dir = opendir(exec_path); + struct dirent *de; + + if (!dir) { + fprintf(stderr, "git: '%s': %s\n", exec_path, strerror(errno)); exit(1); } - i = glob(pattern, 0, NULL, &gl); - switch(i) { - case GLOB_NOSPACE: - puts("Out of memory when running glob()"); - exit(2); - case GLOB_ABORTED: - printf("'%s': Read error: %s\n", exec_path, strerror(errno)); - exit(2); - case GLOB_NOMATCH: - printf("No git commands available in '%s'.\n", exec_path); - printf("Do you need to specify --exec-path or set GIT_EXEC_PATH?\n"); + dirlen = strlen(exec_path); + if (PATH_MAX - 20 < dirlen) { + fprintf(stderr, "git: insanely long exec-path '%s'\n", + exec_path); exit(1); } - for (i = 0; i < gl.gl_pathc; i++) { - int len = strlen(gl.gl_pathv[i] + 4); + memcpy(path, exec_path, dirlen); + path[dirlen++] = '/'; + + while ((de = readdir(dir)) != NULL) { + struct stat st; + int entlen; - if (access(gl.gl_pathv[i], X_OK)) + if (strncmp(de->d_name, "git-", 4)) + continue; + strcpy(path+dirlen, de->d_name); + if (stat(path, &st) || /* stat, not lstat */ + !S_ISREG(st.st_mode) || + !(st.st_mode & S_IXUSR)) continue; - if (longest < len) - longest = len; + entlen = strlen(de->d_name); - if (!tail) - tail = list = malloc(sizeof(struct string_list)); - else { - tail->next = malloc(sizeof(struct string_list)); - tail = tail->next; - } - tail->len = len; - tail->str = gl.gl_pathv[i] + 4; - tail->next = NULL; + if (longest < entlen) + longest = entlen; + + add_cmdname(de->d_name + 4, entlen-4); } + closedir(dir); printf("git commands available in '%s'\n", exec_path); printf("----------------------------"); mput_char('-', strlen(exec_path)); putchar('\n'); - pretty_print_string_list(list, longest); + pretty_print_string_list(cmdname, longest - 4); putchar('\n'); } @@ -146,7 +180,7 @@ static void prepend_to_path(const char *dir, int len) int path_len = len; if (!old_path) - old_path = "/bin:/usr/bin:."; + old_path = "/usr/local/bin:/usr/bin:/bin"; path_len = len + strlen(old_path) + 1; @@ -160,8 +194,6 @@ static void prepend_to_path(const char *dir, int len) setenv("PATH", path, 1); } -/* has anyone seen 'man' installed anywhere else than in /usr/bin? */ -#define PATH_TO_MAN "/usr/bin/man" static void show_man_page(char *git_cmd) { char *page; @@ -177,7 +209,7 @@ static void show_man_page(char *git_cmd) page[page_len] = 0; } - execlp(PATH_TO_MAN, "man", page, NULL); + execlp("man", "man", page, NULL); } int main(int argc, char **argv, char **envp) @@ -226,15 +258,25 @@ int main(int argc, char **argv, char **envp) show_man_page(argv[i]); } - /* allow relative paths, but run with exact */ - if (chdir(exec_path)) { - printf("git: '%s': %s\n", exec_path, strerror(errno)); - exit (1); - } - - getcwd(git_command, sizeof(git_command)); - chdir(wd); + if (*exec_path != '/') { + if (!getcwd(git_command, sizeof(git_command))) { + fprintf(stderr, + "git: cannot determine current directory"); + exit(1); + } + len = strlen(git_command); + /* Trivial cleanup */ + while (!strncmp(exec_path, "./", 2)) { + exec_path += 2; + while (*exec_path == '/') + *exec_path++; + } + snprintf(git_command + len, sizeof(git_command) - len, + "/%s", exec_path); + } + else + strcpy(git_command, exec_path); len = strlen(git_command); prepend_to_path(git_command, len); From c0c35d5e419f3bf215103b1e26359aca288c2113 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 16 Nov 2005 09:38:46 -0800 Subject: [PATCH 18/32] Disallow empty pattern in "git grep" For some reason I've done a "git grep" twice with no pattern, which is really irritating, since it just grep everything. If I actually wanted that, I could do "git grep ^" or something. So add a "usage" message if the pattern is empty. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- git-grep.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/git-grep.sh b/git-grep.sh index e7a35ebd70..44c16130bd 100755 --- a/git-grep.sh +++ b/git-grep.sh @@ -39,5 +39,9 @@ while : ; do esac shift done +[ "$pattern" ] || { + echo >&2 "usage: 'git grep [pathspec*]'" + exit 1 +} git-ls-files -z "${git_flags[@]}" "$@" | xargs -0 grep "${flags[@]}" -e "$pattern" From 8366a10ab21f22deb7ea4a9dbd02effcd279318c Mon Sep 17 00:00:00 2001 From: Pavel Roskin Date: Wed, 16 Nov 2005 13:27:28 -0500 Subject: [PATCH 19/32] symref support for import scripts Fix git import script not to assume that .git/HEAD is a symlink. Signed-off-by: Pavel Roskin Signed-off-by: Junio C Hamano --- git-archimport.perl | 3 +-- git-cvsimport.perl | 9 ++++++--- git-svnimport.perl | 9 ++++++--- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/git-archimport.perl b/git-archimport.perl index e22c81628d..23becb7962 100755 --- a/git-archimport.perl +++ b/git-archimport.perl @@ -410,8 +410,7 @@ foreach my $ps (@psets) { open HEAD, ">$git_dir/refs/heads/$ps->{branch}"; print HEAD $commitid; close HEAD; - unlink ("$git_dir/HEAD"); - symlink("refs/heads/$ps->{branch}","$git_dir/HEAD"); + system('git-update-ref', 'HEAD', "$ps->{branch}"); # tag accordingly ptag($ps->{id}, $commitid); # private tag diff --git a/git-cvsimport.perl b/git-cvsimport.perl index 7bd9136205..efe193439b 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -437,7 +437,11 @@ unless(-d $git_dir) { "Either use the correct '-o branch' option,\n". "or import to a new repository.\n"; - $last_branch = basename(readlink("$git_dir/HEAD")); + open(F, "git-symbolic-ref HEAD |") or + die "Cannot run git-symbolic-ref: $!\n"; + chomp ($last_branch = ); + $last_branch = basename($last_branch); + close(F); unless($last_branch) { warn "Cannot read the last branch name: $! -- assuming 'master'\n"; $last_branch = "master"; @@ -829,8 +833,7 @@ if($orig_branch) { print "DONE; creating $orig_branch branch\n" if $opt_v; system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master") unless -f "$git_dir/refs/heads/master"; - unlink("$git_dir/HEAD"); - symlink("refs/heads/$orig_branch","$git_dir/HEAD"); + system('git-update-ref', 'HEAD', "$orig_branch"); unless ($opt_i) { system('git checkout'); die "checkout failed: $?\n" if $?; diff --git a/git-svnimport.perl b/git-svnimport.perl index af13fdd8e4..45d77c5bae 100755 --- a/git-svnimport.perl +++ b/git-svnimport.perl @@ -216,7 +216,11 @@ unless(-d $git_dir) { -f "$git_dir/svn2git" or die "'$git_dir/svn2git' does not exist.\n". "You need that file for incremental imports.\n"; - $last_branch = basename(readlink("$git_dir/HEAD")); + open(F, "git-symbolic-ref HEAD |") or + die "Cannot run git-symbolic-ref: $!\n"; + chomp ($last_branch = ); + $last_branch = basename($last_branch); + close(F); unless($last_branch) { warn "Cannot read the last branch name: $! -- assuming 'master'\n"; $last_branch = "master"; @@ -766,8 +770,7 @@ if($orig_branch) { print "DONE; creating $orig_branch branch\n" if $opt_v and (not defined $opt_l or $opt_l > 0); system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master") unless -f "$git_dir/refs/heads/master"; - unlink("$git_dir/HEAD"); - symlink("refs/heads/$orig_branch","$git_dir/HEAD"); + system('git-update-ref', 'HEAD', "$orig_branch"); unless ($opt_i) { system('git checkout'); die "checkout failed: $?\n" if $?; From 565cb991147d7bf7945ed50d9d5e1e84ade35572 Mon Sep 17 00:00:00 2001 From: Alecs King Date: Thu, 17 Nov 2005 03:06:03 +0800 Subject: [PATCH 20/32] Documentation/git-log.txt: trivial typo fix. Signed-off-by: Alecs King Signed-off-by: Junio C Hamano --- Documentation/git-log.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index 9cac0886cf..e995d1b74b 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -20,7 +20,7 @@ This manual page describes only the most frequently used options. OPTIONS ------- ---pretty=: +--pretty=:: Controls the way the commit log is formatted. --max-count=:: From 0ff2ce9d8a305038c0c116cd1e3b478a9f5b81c1 Mon Sep 17 00:00:00 2001 From: Kevin Geiss Date: Mon, 14 Nov 2005 09:45:05 -0700 Subject: [PATCH 21/32] git-cvsexportcommit.perl: Fix usage() output. Signed-off-by: Junio C Hamano --- git-cvsexportcommit.perl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index 50b041c324..5bce39c4c4 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -4,6 +4,7 @@ use strict; use Getopt::Std; use File::Temp qw(tempdir); use Data::Dumper; +use File::Basename qw(basename); unless ($ENV{GIT_DIR} && -r $ENV{GIT_DIR}){ die "GIT_DIR is not defined or is unreadable"; @@ -206,8 +207,7 @@ if ($opt_c) { } sub usage { print STDERR < Date: Mon, 14 Nov 2005 09:41:43 -0700 Subject: [PATCH 22/32] git-cvsexportcommit.perl: use getopts to get binary flags Signed-off-by: Junio C Hamano --- git-cvsexportcommit.perl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index 5bce39c4c4..da7dcda3b6 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -12,7 +12,7 @@ unless ($ENV{GIT_DIR} && -r $ENV{GIT_DIR}){ our ($opt_h, $opt_p, $opt_v, $opt_c ); -getopt('hpvc'); +getopts('hpvc'); $opt_h && usage(); From 8b3fbeef395295c0d2de61265632ed30eda7bf0a Mon Sep 17 00:00:00 2001 From: Kevin Geiss Date: Mon, 14 Nov 2005 09:42:36 -0700 Subject: [PATCH 23/32] git-cvsexportcommit.perl: exit with non-0 status if patch fails. Signed-off-by: Junio C Hamano --- git-cvsexportcommit.perl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index da7dcda3b6..d49494abc2 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -190,7 +190,7 @@ if ($dirtypatch) { print "NOTE: One or more hunks failed to apply cleanly.\n"; print "Resolve the conflicts and then commit using:n"; print "\n $cmd\n\n"; - exit; + exit(1); } From 5b4525eb8b6a4a131d0cc10075165829f754dcc2 Mon Sep 17 00:00:00 2001 From: Kevin Geiss Date: Mon, 14 Nov 2005 09:43:51 -0700 Subject: [PATCH 24/32] git-cvsexportcommit.perl: fix typos in output Signed-off-by: Junio C Hamano --- git-cvsexportcommit.perl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index d49494abc2..5a8c011802 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -78,7 +78,7 @@ $opt_v && print "Applying to CVS commit $commit from parent $parent\n"; # grab the commit message `git-cat-file commit $commit | sed -e '1,/^\$/d' > .msg`; -$? && die "Error extraction the commit message"; +$? && die "Error extracting the commit message"; my (@afiles, @dfiles, @mfiles); my @files = `git-diff-tree -r $parent $commit`; @@ -188,7 +188,7 @@ my $cmd = "cvs commit -F .msg $commitfiles"; if ($dirtypatch) { print "NOTE: One or more hunks failed to apply cleanly.\n"; - print "Resolve the conflicts and then commit using:n"; + print "Resolve the conflicts and then commit using:\n"; print "\n $cmd\n\n"; exit(1); } From 92927ed0aac56a86f85049215791fcd106af4b62 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 16 Nov 2005 14:12:56 -0800 Subject: [PATCH 25/32] git-apply: fail if a patch cannot be applied. Recently we fixed 'git-apply --stat' not to barf on a binary differences. But it accidentally broke the error detection when we actually attempt to apply them. This commit fixes the problem and adds test cases. Signed-off-by: Junio C Hamano --- apply.c | 11 +++--- t/t4103-apply-binary.sh | 78 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 t/t4103-apply-binary.sh diff --git a/apply.c b/apply.c index 590adc6afa..a002e156c7 100644 --- a/apply.c +++ b/apply.c @@ -891,7 +891,7 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) patchsize = parse_single_patch(buffer + offset + hdrsize, size - offset - hdrsize, patch); - if (!patchsize && !metadata_changes(patch)) { + if (!patchsize) { static const char binhdr[] = "Binary files "; if (sizeof(binhdr) - 1 < size - offset - hdrsize && @@ -899,9 +899,12 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) sizeof(binhdr)-1)) patch->is_binary = 1; - if (patch->is_binary && !apply && !check) - ; - else + /* Empty patch cannot be applied if: + * - it is a binary patch or + * - metadata does not change and is not a binary patch. + */ + if ((apply || check) && + (patch->is_binary || !metadata_changes(patch))) die("patch with only garbage at line %d", linenr); } diff --git a/t/t4103-apply-binary.sh b/t/t4103-apply-binary.sh new file mode 100644 index 0000000000..948d5b5a7a --- /dev/null +++ b/t/t4103-apply-binary.sh @@ -0,0 +1,78 @@ +#!/bin/sh +# +# Copyright (c) 2005 Junio C Hamano +# + +test_description='git-apply handling binary patches + +' +. ./test-lib.sh + +# setup + +cat >file1 <file2 +cat file1 >file4 + +git-update-index --add --remove file1 file2 file4 +git-commit -m 'Initial Version' 2>/dev/null + +git-checkout -b binary +tr 'x' '\0' file3 +cat file3 >file4 +git-add file2 +tr '\0' 'v' file1 +rm -f file2 +git-update-index --add --remove file1 file2 file3 file4 +git-commit -m 'Second Version' + +git-diff-tree -p master binary >B.diff +git-diff-tree -p -C master binary >C.diff + +test_expect_success 'stat binary diff -- should not fail.' \ + 'git-checkout master + git-apply --stat --summary B.diff' + +test_expect_success 'stat binary diff (copy) -- should not fail.' \ + 'git-checkout master + git-apply --stat --summary C.diff' + +test_expect_failure 'check binary diff -- should fail.' \ + 'git-checkout master + git-apply --check B.diff' + +test_expect_failure 'check binary diff (copy) -- should fail.' \ + 'git-checkout master + git-apply --check C.diff' + +# Now we start applying them. + +test_expect_failure 'apply binary diff -- should fail.' \ + 'git-checkout master + git-apply B.diff' + +git-reset --hard + +test_expect_failure 'apply binary diff -- should fail.' \ + 'git-checkout master + git-apply --index B.diff' + +git-reset --hard + +test_expect_failure 'apply binary diff (copy) -- should fail.' \ + 'git-checkout master + git-apply C.diff' + +git-reset --hard + +test_expect_failure 'apply binary diff (copy) -- should fail.' \ + 'git-checkout master + git-apply --index C.diff' + +test_done From 0c15cc921a54a2cd5f0758041fe9fdd715a493e8 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 16 Nov 2005 00:19:32 -0800 Subject: [PATCH 26/32] git-am: --resolved. After failed patch application, you can manually apply the patch (this includes resolving the conflicted merge after git-am falls back to 3-way merge) and run git-update-index on necessary paths to prepare the index file in a shape a successful patch application should have produced. Then re-running git-am --resolved would record the resulting index file along with the commit log information taken from the patch e-mail. Signed-off-by: Junio C Hamano --- git-am.sh | 65 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 12 deletions(-) diff --git a/git-am.sh b/git-am.sh index 38841d9bee..98a390ab45 100755 --- a/git-am.sh +++ b/git-am.sh @@ -3,16 +3,10 @@ # . git-sh-setup || die "Not a git archive" -files=$(git-diff-index --cached --name-only HEAD) || exit -if [ "$files" ]; then - echo "Dirty index: cannot apply patches (dirty: $files)" >&2 - exit 1 -fi - usage () { echo >&2 "usage: $0 [--signoff] [--dotest=] [--utf8] [--3way] " echo >&2 " or, when resuming" - echo >&2 " $0 [--skip]" + echo >&2 " $0 [--skip | --resolved]" exit 1; } @@ -104,7 +98,7 @@ fall_back_3way () { } prec=4 -dotest=.dotest sign= utf8= keep= skip= interactive= +dotest=.dotest sign= utf8= keep= skip= interactive= resolved= while case "$#" in 0) break;; esac do @@ -128,6 +122,9 @@ do -k|--k|--ke|--kee|--keep) keep=t; shift ;; + -r|--r|--re|--res|--reso|--resol|--resolv|--resolve|--resolved) + resolved=t; shift ;; + --sk|--ski|--skip) skip=t; shift ;; @@ -140,6 +137,8 @@ do esac done +# If the dotest directory exists, but we have finished applying all the +# patches in them, clear it out. if test -d "$dotest" && last=$(cat "$dotest/last") && next=$(cat "$dotest/next") && @@ -155,9 +154,9 @@ then die "previous dotest directory $dotest still exists but mbox given." resume=yes else - # Make sure we are not given --skip - test ",$skip," = ,, || - die "we are not resuming." + # Make sure we are not given --skip nor --resolved + test ",$skip,$resolved," = ,,, || + die "we are not resuming." # Start afresh. mkdir -p "$dotest" || exit @@ -170,12 +169,24 @@ else exit 1 } + # -s, -u and -k flags are kept for the resuming session after + # a patch failure. + # -3 and -i can and must be given when resuming. echo "$sign" >"$dotest/sign" echo "$utf8" >"$dotest/utf8" echo "$keep" >"$dotest/keep" echo 1 >"$dotest/next" fi +case "$resolved" in +'') + files=$(git-diff-index --cached --name-only HEAD) || exit + if [ "$files" ]; then + echo "Dirty index: cannot apply patches (dirty: $files)" >&2 + exit 1 + fi +esac + if test "$(cat "$dotest/utf8")" = t then utf8=-u @@ -216,6 +227,15 @@ do go_next continue } + + # If we are not resuming, parse and extract the patch information + # into separate files: + # - info records the authorship and title + # - msg is the rest of commit log message + # - patch is the patch body. + # + # When we are resuming, these files are either already prepared + # by the user, or the user can tell us to do so by --resolved flag. case "$resume" in '') git-mailinfo $keep $utf8 "$dotest/msg" "$dotest/patch" \ @@ -263,6 +283,13 @@ do fi } >"$dotest/final-commit" ;; + *) + case "$resolved,$interactive" in + tt) + # This is used only for interactive view option. + git-diff-index -p --cached HEAD >"$dotest/patch" + ;; + esac esac resume= @@ -310,7 +337,21 @@ do echo "Applying '$SUBJECT'" echo - git-apply --index "$dotest/patch"; apply_status=$? + case "$resolved" in + '') + git-apply --index "$dotest/patch" + apply_status=$? + ;; + t) + # Resolved means the user did all the hard work, and + # we do not have to do any patch application. Just + # trust what the user has in the index file and the + # working tree. + resolved= + apply_status=0 + ;; + esac + if test $apply_status = 1 && test "$threeway" = t then if (fall_back_3way) From 011f4274bbb14e44035586f2ede695d584b8cfd2 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 14 Nov 2005 17:37:05 -0800 Subject: [PATCH 27/32] apply: allow-binary-replacement. A new option, --allow-binary-replacement, is introduced. When you feed a diff that records full SHA1 name of pre- and post-image blob on its index line to git-apply with this option, the post-image blob replaces the path if what you have in the working tree matches the pre-image _and_ post-image blob is already available in the object directory. Later we _might_ want to enhance the diff output to also include the full binary data of the post-image, to make this more useful, but this is good enough for local rebasing application. Signed-off-by: Junio C Hamano --- Documentation/git-apply.txt | 13 +++++- apply.c | 87 ++++++++++++++++++++++++++++++++++--- t/t4103-apply-binary.sh | 8 ++++ 3 files changed, 101 insertions(+), 7 deletions(-) diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index 6702a18c7e..626e281596 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -8,7 +8,7 @@ git-apply - Apply patch on a git index file and a work tree SYNOPSIS -------- -'git-apply' [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [-z] [...] +'git-apply' [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [...] DESCRIPTION ----------- @@ -79,6 +79,17 @@ OPTIONS the result with this option, which would apply the deletion part but not addition part. +--allow-binary-replacement:: + When applying a patch, which is a git-enhanced patch + that was prepared to record the pre- and post-image object + name in full, and the path being patched exactly matches + the object the patch applies to (i.e. "index" line's + pre-image object name is what is in the working tree), + and the post-image object is available in the object + database, use the post-image object as the patch + result. This allows binary files to be patched in a + very limited way. + Author ------ Written by Linus Torvalds diff --git a/apply.c b/apply.c index a002e156c7..129edb1889 100644 --- a/apply.c +++ b/apply.c @@ -16,6 +16,7 @@ // --numstat does numeric diffstat, and doesn't actually apply // --index-info shows the old and new index info for paths if available. // +static int allow_binary_replacement = 0; static int check_index = 0; static int write_index = 0; static int diffstat = 0; @@ -27,7 +28,7 @@ static int no_add = 0; static int show_index_info = 0; static int line_termination = '\n'; static const char apply_usage[] = -"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [-z] ..."; +"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] ..."; /* * For "diff-stat" like behaviour, we keep track of the biggest change @@ -900,11 +901,13 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) patch->is_binary = 1; /* Empty patch cannot be applied if: - * - it is a binary patch or - * - metadata does not change and is not a binary patch. + * - it is a binary patch and we do not do binary_replace, or + * - text patch without metadata change */ if ((apply || check) && - (patch->is_binary || !metadata_changes(patch))) + (patch->is_binary + ? !allow_binary_replacement + : !metadata_changes(patch))) die("patch with only garbage at line %d", linenr); } @@ -1158,10 +1161,77 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag) static int apply_fragments(struct buffer_desc *desc, struct patch *patch) { struct fragment *frag = patch->fragments; + const char *name = patch->old_name ? patch->old_name : patch->new_name; + + if (patch->is_binary) { + unsigned char sha1[20]; + + if (!allow_binary_replacement) + return error("cannot apply binary patch to '%s' " + "without --allow-binary-replacement", + name); + + /* For safety, we require patch index line to contain + * full 40-byte textual SHA1 for old and new, at least for now. + */ + if (strlen(patch->old_sha1_prefix) != 40 || + strlen(patch->new_sha1_prefix) != 40 || + get_sha1_hex(patch->old_sha1_prefix, sha1) || + get_sha1_hex(patch->new_sha1_prefix, sha1)) + return error("cannot apply binary patch to '%s' " + "without full index line", name); + + if (patch->old_name) { + unsigned char hdr[50]; + int hdrlen; + + /* See if the old one matches what the patch + * applies to. + */ + write_sha1_file_prepare(desc->buffer, desc->size, + "blob", sha1, hdr, &hdrlen); + if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix)) + return error("the patch applies to '%s' (%s), " + "which does not match the " + "current contents.", + name, sha1_to_hex(sha1)); + } + else { + /* Otherwise, the old one must be empty. */ + if (desc->size) + return error("the patch applies to an empty " + "'%s' but it is not empty", name); + } + + /* For now, we do not record post-image data in the patch, + * and require the object already present in the recipient's + * object database. + */ + if (desc->buffer) { + free(desc->buffer); + desc->alloc = desc->size = 0; + } + get_sha1_hex(patch->new_sha1_prefix, sha1); + + if (memcmp(sha1, null_sha1, 20)) { + char type[10]; + unsigned long size; + + desc->buffer = read_sha1_file(sha1, type, &size); + if (!desc->buffer) + return error("the necessary postimage %s for " + "'%s' does not exist", + patch->new_sha1_prefix, name); + desc->alloc = desc->size = size; + } + + return 0; + } while (frag) { if (apply_one_fragment(desc, frag) < 0) - return error("patch failed: %s:%ld", patch->old_name, frag->oldpos); + return error("patch failed: %s:%ld", + name, frag->oldpos); frag = frag->next; } return 0; @@ -1203,6 +1273,7 @@ static int check_patch(struct patch *patch) struct stat st; const char *old_name = patch->old_name; const char *new_name = patch->new_name; + const char *name = old_name ? old_name : new_name; if (old_name) { int changed; @@ -1277,7 +1348,7 @@ static int check_patch(struct patch *patch) } if (apply_data(patch, &st) < 0) - return error("%s: patch does not apply", old_name); + return error("%s: patch does not apply", name); return 0; } @@ -1726,6 +1797,10 @@ int main(int argc, char **argv) diffstat = 1; continue; } + if (!strcmp(arg, "--allow-binary-replacement")) { + allow_binary_replacement = 1; + continue; + } if (!strcmp(arg, "--numstat")) { apply = 0; numstat = 1; diff --git a/t/t4103-apply-binary.sh b/t/t4103-apply-binary.sh index 948d5b5a7a..9057f6d04f 100644 --- a/t/t4103-apply-binary.sh +++ b/t/t4103-apply-binary.sh @@ -51,6 +51,14 @@ test_expect_failure 'check binary diff (copy) -- should fail.' \ 'git-checkout master git-apply --check C.diff' +test_expect_failure 'check incomplete binary diff with replacement -- should fail.' \ + 'git-checkout master + git-apply --check --allow-binary-replacement B.diff' + +test_expect_failure 'check incomplete binary diff with replacement (copy) -- should fail.' \ + 'git-checkout master + git-apply --check --allow-binary-replacement C.diff' + # Now we start applying them. test_expect_failure 'apply binary diff -- should fail.' \ From 80b1e511d74ac2942043f912d850cb6b85b44689 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 14 Nov 2005 17:53:22 -0800 Subject: [PATCH 28/32] diff: --full-index A new option, --full-index, is introduced to diff family. This causes the full object name of pre- and post-images to appear on the index line of patch formatted output, to be used in conjunction with --allow-binary-replacement option of git-apply. Signed-off-by: Junio C Hamano --- Documentation/diff-options.txt | 5 +++++ diff.c | 14 ++++++++------ diff.h | 4 +++- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 8eef86e474..6b496ede25 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -13,6 +13,11 @@ --name-status:: Show only names and status of changed files. +--full-index:: + Instead of the first handful characters, show full + object name of pre- and post-image blob on the "index" + line when generating a patch format output. + -B:: Break complete rewrite changes into pairs of delete and create. diff --git a/diff.c b/diff.c index fca61f32f0..0391e8c423 100644 --- a/diff.c +++ b/diff.c @@ -650,7 +650,7 @@ static void diff_fill_sha1_info(struct diff_filespec *one) memset(one->sha1, 0, 20); } -static void run_diff(struct diff_filepair *p) +static void run_diff(struct diff_filepair *p, struct diff_options *o) { const char *pgm = external_diff(); char msg[PATH_MAX*2+300], *xfrm_msg; @@ -713,11 +713,11 @@ static void run_diff(struct diff_filepair *p) if (memcmp(one->sha1, two->sha1, 20)) { char one_sha1[41]; + const char *index_fmt = o->full_index ? "index %s..%s" : "index %.7s..%.7s"; memcpy(one_sha1, sha1_to_hex(one->sha1), 41); len += snprintf(msg + len, sizeof(msg) - len, - "index %.7s..%.7s", one_sha1, - sha1_to_hex(two->sha1)); + index_fmt, one_sha1, sha1_to_hex(two->sha1)); if (one->mode == two->mode) len += snprintf(msg + len, sizeof(msg) - len, " %06o", one->mode); @@ -794,6 +794,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->line_termination = 0; else if (!strncmp(arg, "-l", 2)) options->rename_limit = strtoul(arg+2, NULL, 10); + else if (!strcmp(arg, "--full-index")) + options->full_index = 1; else if (!strcmp(arg, "--name-only")) options->output_format = DIFF_FORMAT_NAME; else if (!strcmp(arg, "--name-status")) @@ -1022,7 +1024,7 @@ int diff_unmodified_pair(struct diff_filepair *p) return 0; } -static void diff_flush_patch(struct diff_filepair *p) +static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o) { if (diff_unmodified_pair(p)) return; @@ -1031,7 +1033,7 @@ static void diff_flush_patch(struct diff_filepair *p) (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode))) return; /* no tree diffs in patch format */ - run_diff(p); + run_diff(p, o); } int diff_queue_is_empty(void) @@ -1163,7 +1165,7 @@ void diff_flush(struct diff_options *options) die("internal error in diff-resolve-rename-copy"); switch (diff_output_format) { case DIFF_FORMAT_PATCH: - diff_flush_patch(p); + diff_flush_patch(p, options); break; case DIFF_FORMAT_RAW: case DIFF_FORMAT_NAME_STATUS: diff --git a/diff.h b/diff.h index 12590791cb..9b2e1e62bb 100644 --- a/diff.h +++ b/diff.h @@ -32,7 +32,8 @@ struct diff_options { const char *orderfile; const char *pickaxe; unsigned recursive:1, - tree_in_recursive:1; + tree_in_recursive:1, + full_index:1; int break_opt; int detect_rename; int find_copies_harder; @@ -96,6 +97,7 @@ extern void diffcore_std_no_resolve(struct diff_options *); " -u synonym for -p.\n" \ " --name-only show only names of changed files.\n" \ " --name-status show names and status of changed files.\n" \ +" --full-index show full object name on index ines.\n" \ " -R swap input file pairs.\n" \ " -B detect complete rewrites.\n" \ " -M detect renames.\n" \ From fbba222f5de21aafa566430a8ae7cf6f4ab163b1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 16 Nov 2005 15:52:45 -0800 Subject: [PATCH 29/32] tests: binary diff application. This adds more tests to cover cases where binary diff application succeeds. Signed-off-by: Junio C Hamano --- t/t4103-apply-binary.sh | 49 ++++++++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/t/t4103-apply-binary.sh b/t/t4103-apply-binary.sh index 9057f6d04f..00bd8b15c6 100644 --- a/t/t4103-apply-binary.sh +++ b/t/t4103-apply-binary.sh @@ -35,6 +35,9 @@ git-commit -m 'Second Version' git-diff-tree -p master binary >B.diff git-diff-tree -p -C master binary >C.diff +git-diff-tree -p --full-index master binary >BF.diff +git-diff-tree -p --full-index -C master binary >CF.diff + test_expect_success 'stat binary diff -- should not fail.' \ 'git-checkout master git-apply --stat --summary B.diff' @@ -59,28 +62,54 @@ test_expect_failure 'check incomplete binary diff with replacement (copy) -- sho 'git-checkout master git-apply --check --allow-binary-replacement C.diff' +test_expect_success 'check binary diff with replacement.' \ + 'git-checkout master + git-apply --check --allow-binary-replacement BF.diff' + +test_expect_success 'check binary diff with replacement (copy).' \ + 'git-checkout master + git-apply --check --allow-binary-replacement CF.diff' + # Now we start applying them. +do_reset () { + rm -f file? + git-reset --hard + git-checkout -f master +} + test_expect_failure 'apply binary diff -- should fail.' \ - 'git-checkout master + 'do_reset git-apply B.diff' -git-reset --hard - test_expect_failure 'apply binary diff -- should fail.' \ - 'git-checkout master + 'do_reset git-apply --index B.diff' -git-reset --hard - test_expect_failure 'apply binary diff (copy) -- should fail.' \ - 'git-checkout master + 'do_reset git-apply C.diff' -git-reset --hard - test_expect_failure 'apply binary diff (copy) -- should fail.' \ - 'git-checkout master + 'do_reset git-apply --index C.diff' +test_expect_failure 'apply binary diff without replacement -- should fail.' \ + 'do_reset + git-apply BF.diff' + +test_expect_failure 'apply binary diff without replacement (copy) -- should fail.' \ + 'do_reset + git-apply CF.diff' + +test_expect_success 'apply binary diff.' \ + 'do_reset + git-apply --allow-binary-replacement --index BF.diff && + test -z "$(git-diff --name-status binary)"' + +test_expect_success 'apply binary diff (copy).' \ + 'do_reset + git-apply --allow-binary-replacement --index CF.diff && + test -z "$(git-diff --name-status binary)"' + test_done From a8883288fa1240996a9c5715e060a88a03796fe0 Mon Sep 17 00:00:00 2001 From: Andreas Ericsson Date: Thu, 17 Nov 2005 00:38:29 +0100 Subject: [PATCH 30/32] daemon.c: fix arg parsing bugs Allow --init-timeout and --timeout to be specified without falling through to usage(). Make sure openlog() is called even if implied by --inetd, or messages will be sent to wherever LOG_USER ends up. Signed-off-by: Andreas Ericsson Signed-off-by: Junio C Hamano --- daemon.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/daemon.c b/daemon.c index e184752298..2b81152d71 100644 --- a/daemon.c +++ b/daemon.c @@ -594,6 +594,7 @@ int main(int argc, char **argv) } if (!strcmp(arg, "--inetd")) { inetd_mode = 1; + log_syslog = 1; continue; } if (!strcmp(arg, "--verbose")) { @@ -602,7 +603,6 @@ int main(int argc, char **argv) } if (!strcmp(arg, "--syslog")) { log_syslog = 1; - openlog("git-daemon", 0, LOG_DAEMON); continue; } if (!strcmp(arg, "--export-all")) { @@ -611,9 +611,11 @@ int main(int argc, char **argv) } if (!strncmp(arg, "--timeout=", 10)) { timeout = atoi(arg+10); + continue; } if (!strncmp(arg, "--init-timeout=", 15)) { init_timeout = atoi(arg+15); + continue; } if (!strcmp(arg, "--")) { ok_paths = &argv[i+1]; @@ -626,9 +628,11 @@ int main(int argc, char **argv) usage(daemon_usage); } + if (log_syslog) + openlog("git-daemon", 0, LOG_DAEMON); + if (inetd_mode) { fclose(stderr); //FIXME: workaround - log_syslog = 1; return execute(); } From 3c07b1d19491aa9acb9f8e86486f0b80f976edf9 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 14 Nov 2005 19:29:06 -0800 Subject: [PATCH 31/32] git's rev-parse.c function show_datestring presumes gnu date Ok. This is the insane patch to do this. It really isn't very careful, and the reason I call it "approxidate()" will become obvious when you look at the code. It is very liberal in what it accepts, to the point where sometimes the results may not make a whole lot of sense. It accepts "last week" as a date string, by virtue of "last" parsing as the number 1, and it totally ignoring superfluous fluff like "ago", so "last week" ends up being exactly the same thing as "1 week ago". Fine so far. It has strange side effects: "last december" will actually parse as "Dec 1", which actually _does_ turn out right, because it will then notice that it's not December yet, so it will decide that you must be talking about a date last year. So it actually gets it right, but it's kind of for the "wrong" reasons. It also accepts the numbers 1..10 in string format ("one" .. "ten"), so you can do "ten weeks ago" or "ten hours ago" and it will do the right thing. But it will do some really strange thigns too: the string "this will last forever", will not recognize anyting but "last", which is recognized as "1", which since it doesn't understand anything else it will think is the day of the month. So if you do gitk --since="this will last forever" the date will actually parse as the first day of the current month. And it will parse the string "now" as "now", but only because it doesn't understand it at all, and it makes everything relative to "now". Similarly, it doesn't actually parse the "ago" or "from now", so "2 weeks ago" is exactly the same as "2 weeks from now". It's the current date minus 14 days. But hey, it's probably better (and certainly faster) than depending on GNU date. So now you can portably do things like gitk --since="two weeks and three days ago" git log --since="July 5" git-whatchanged --since="10 hours ago" git log --since="last october" and it will actually do exactly what you thought it would do (I think). It will count 17 days backwards, and it will do so even if you don't have GNU date installed. (I don't do "last monday" or similar yet, but I can extend it to that too if people want). It was kind of fun trying to write code that uses such totally relaxed "understanding" of dates yet tries to get it right for the trivial cases. The result should be mixed with a few strange preprocessor tricks, and be submitted for the IOCCC ;) Feel free to try it out, and see how many strange dates it gets right. Or wrong. And if you find some interesting (and valid - not "interesting" as in "strange", but "interesting" as in "I'd be interested in actually doing this) thing it gets wrong - usually by not understanding it and silently just doing some strange things - please holler. Now, as usual this certainly hasn't been getting a lot of testing. But my code always works, no? Linus Signed-off-by: Junio C Hamano --- cache.h | 1 + date.c | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++++ rev-parse.c | 15 +------ 3 files changed, 126 insertions(+), 14 deletions(-) diff --git a/cache.h b/cache.h index 08461cfc5a..99afa2c3c4 100644 --- a/cache.h +++ b/cache.h @@ -259,6 +259,7 @@ extern void *read_object_with_reference(const unsigned char *sha1, const char *show_date(unsigned long time, int timezone); int parse_date(const char *date, char *buf, int bufsize); void datestamp(char *buf, int bufsize); +unsigned long approxidate(const char *); extern int setup_ident(void); extern char *get_ident(const char *name, const char *email, const char *date_str); diff --git a/date.c b/date.c index 63f5a09197..73c063b9ab 100644 --- a/date.c +++ b/date.c @@ -5,6 +5,7 @@ */ #include +#include #include "cache.h" @@ -460,3 +461,126 @@ void datestamp(char *buf, int bufsize) date_string(now, offset, buf, bufsize); } + +static void update_tm(struct tm *tm, unsigned long sec) +{ + time_t n = mktime(tm) - sec; + localtime_r(&n, tm); +} + +static const char *number_name[] = { + "zero", "one", "two", "three", "four", + "five", "six", "seven", "eight", "nine", "ten", +}; + +static struct typelen { + const char *type; + int length; +} typelen[] = { + { "seconds", 1 }, + { "minutes", 60 }, + { "hours", 60*60 }, + { "days", 24*60*60 }, + { "weeks", 7*24*60*60 }, + { NULL } +}; + +static const char *approxidate_alpha(const char *date, struct tm *tm, int *num) +{ + struct typelen *tl; + const char *end = date; + int n = 1, i; + + while (isalpha(*++end)) + n++; + + for (i = 0; i < 12; i++) { + int match = match_string(date, month_names[i]); + if (match >= 3) { + tm->tm_mon = i; + return end; + } + } + + if (match_string(date, "yesterday") > 8) { + update_tm(tm, 24*60*60); + return end; + } + + if (!*num) { + for (i = 1; i < 11; i++) { + int len = strlen(number_name[i]); + if (match_string(date, number_name[i]) == len) { + *num = i; + return end; + } + } + if (match_string(date, "last") == 4) + *num = 1; + return end; + } + + tl = typelen; + while (tl->type) { + int len = strlen(tl->type); + if (match_string(date, tl->type) >= len-1) { + update_tm(tm, tl->length * *num); + *num = 0; + return end; + } + tl++; + } + + if (match_string(date, "months") >= 5) { + int n = tm->tm_mon - *num; + *num = 0; + while (n < 0) { + n += 12; + tm->tm_year--; + } + tm->tm_mon = n; + return end; + } + + if (match_string(date, "years") >= 4) { + tm->tm_year -= *num; + *num = 0; + return end; + } + + return end; +} + +unsigned long approxidate(const char *date) +{ + int number = 0; + struct tm tm, now; + struct timeval tv; + char buffer[50]; + + if (parse_date(date, buffer, sizeof(buffer)) > 0) + return strtoul(buffer, NULL, 10); + + gettimeofday(&tv, NULL); + localtime_r(&tv.tv_sec, &tm); + now = tm; + for (;;) { + unsigned char c = *date; + if (!c) + break; + date++; + if (isdigit(c)) { + char *end; + number = strtoul(date-1, &end, 10); + date = end; + continue; + } + if (isalpha(c)) + date = approxidate_alpha(date-1, &tm, &number); + } + if (number > 0 && number < 32) + tm.tm_mday = number; + if (tm.tm_mon > now.tm_mon) + tm.tm_year--; + return mktime(&tm); +} diff --git a/rev-parse.c b/rev-parse.c index 5a98982511..bb4949ad70 100644 --- a/rev-parse.c +++ b/rev-parse.c @@ -131,25 +131,12 @@ static int show_reference(const char *refname, const unsigned char *sha1) static void show_datestring(const char *flag, const char *datestr) { - FILE *date; static char buffer[100]; - static char cmd[1000]; - int len; /* date handling requires both flags and revs */ if ((filter & (DO_FLAGS | DO_REVS)) != (DO_FLAGS | DO_REVS)) return; - len = strlen(flag); - memcpy(buffer, flag, len); - - snprintf(cmd, sizeof(cmd), "date --date=%s +%%s", sq_quote(datestr)); - date = popen(cmd, "r"); - if (!date || !fgets(buffer + len, sizeof(buffer) - len, date)) - die("git-rev-list: bad date string"); - pclose(date); - len = strlen(buffer); - if (buffer[len-1] == '\n') - buffer[--len] = 0; + snprintf(buffer, sizeof(buffer), "%s%lu", flag, approxidate(datestr)); show(buffer); } From f30c95dd76800fbd66fb66180d67c09bab678282 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 15 Nov 2005 00:07:04 -0800 Subject: [PATCH 32/32] Add approxidate test calls. Signed-off-by: Junio C Hamano --- test-date.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test-date.c b/test-date.c index 6fe3e28b9d..93e802759f 100644 --- a/test-date.c +++ b/test-date.c @@ -15,6 +15,9 @@ int main(int argc, char **argv) parse_date(argv[i], result, sizeof(result)); t = strtoul(result, NULL, 0); printf("%s -> %s -> %s", argv[i], result, ctime(&t)); + + t = approxidate(argv[i]); + printf("%s -> %s\n", argv[i], ctime(&t)); } return 0; }