Git development
 help / color / mirror / Atom feed
* Re: contrib/ area
From: Junio C Hamano @ 2006-02-21  3:58 UTC (permalink / raw)
  To: git; +Cc: Ben Clifford
In-Reply-To: <Pine.OSX.4.64.0602201737260.16179@piva.hawaga.org.uk>

Ben Clifford <benc@hawaga.org.uk> writes:

> I have been keeping some (lazily-maintained) bash completion code at:
>
> http://www.hawaga.org.uk/ben/tech/gitcompletion/
>
> It isn't clear to me whether it should stay there or be merged into
> git/contrib/.
> ...
> The path of least resistence for me is to keep it at the above
> hawaga.org.uk URL, but it may be that some people would prefer it in
> the main repos.

I am OK either way.  They are small enough to be in contrib/
area, and bash users may appreciate easier availability.  What I
would _refuse_ to do is to maintain code for other people ;-).
As long as you or somebody else is going to keep them updated as
needed, I do not mind carrying them in contrib/.

As a practice for doing "even cooler merge", I did the
following, to see if I can treat it just like I treat gitk.

    (1) give you a topic branch.
    $ git checkout -b bc/completion master

    (2) fetch your tip
    $ git fetch http://www.hawaga.org.uk/gitcompletion.git/

    (3) for a practice of a later "renaming" merge, pretend you were
        one rev behind than you actually are.
    $ COMMIT=`git rev-parse FETCH_HEAD^`

    (4) extract things under contrib/completion/
    $ git read-tree $COMMIT
    $ git checkout-index --prefix=contrib/completion/ -a

    (5) throw the index away, and add the files back into the real index.
    $ git reset
    $ git add contrib/completion

    (6) write the result of "even cooler merge" out, with a merge
        commit message:
    $ T=`git-write-tree`
    $ sed -e 's/^[0-9a-f]*/'$COMMIT/ <.git/FETCH_HEAD |
      git-fmt-merge-msg |
      git commit-tree $T -p `git rev-parse HEAD` -p $COMMIT

    (7) a practice of later merge
    $ git pull -s recursive http://www.hawaga.org.uk/gitcompletion.git/

The result is sitting at the tip of "pu" branch.

^ permalink raw reply

* Re: Delta-transfer using thin-pack
From: Martin Langhoff @ 2006-02-21  2:05 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <7voe13exoq.fsf@assigned-by-dhcp.cox.net>

On 2/20/06, Junio C Hamano <junkio@cox.net> wrote:
> I'll be sending out three experimental patches that would come
> on top of "next".  With them, you should be able to:

I see that an updated version of this has made it to next & pu.
Cool stuff -- fetched, made, and playing w it ;-)


m

^ permalink raw reply

* Re: contrib/ area
From: Ben Clifford @ 2006-02-21  1:41 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano, Aneesh Kumar
In-Reply-To: <7vmzgq451m.fsf@assigned-by-dhcp.cox.net>



> As some of you may be aware, I've added contrib/ area to git.git
> repository.  Currently there are git-svn from Eric and gitview
> from Aneesh.
>
> The intention is to keep interesting tools around git, maybe
> even experimental ones, to give users an easier access to them,
> and to give tools wider exposure, so that they can be improved
> faster.

I have been keeping some (lazily-maintained) bash completion code at:

http://www.hawaga.org.uk/ben/tech/gitcompletion/

It isn't clear to me whether it should stay there or be merged into 
git/contrib/.

Cogito has the cogito bits of the above already included.

Stg doesn't.

The path of least resistence for me is to keep it at the above 
hawaga.org.uk URL, but it may be that some people would prefer it in the 
main repos.

Ben

-- 

^ permalink raw reply

* Ideas for qgit
From: Pavel Roskin @ 2006-02-21  1:16 UTC (permalink / raw)
  To: Marco Costalba, git

Hello, Marco!

Many thanks for releasing qgit 1.1!  Here are things I'd like to be
fixed in the next version.

The pop-up menu on revisions gets duplicate entries for tags after using
the file viewer (try qgit on the qgit repository).  This is a pure bug
that may merit a minor release (1.1.1 perhaps).

Tags should be in a submenu.  There are too many of them in some
projects.  Maybe there should be a limit of e.g. 10 tags, and the "more
tags" entry that would invoke a dialog for searching tags (and possibly
other things).

If all branches are loaded, it would be nice to be able to switch
between branches in a similar way.

The "check working dir" feature picks up files unknown to git, such as
editor backups and diff files.  It is very rare that a commit only has
added files but no files are modified.  Maybe I need to learn a habit of
adding any file I'm not going to commit to .gitignore, but as it stands
now, this feature of qgit seems too obtrusive to me.  Neither "cg-diff"
not "stg diff" will pick untracked files, and it's not even an option
for either of them.

Current StGIT creates entries for all patches under .git/refs/patches,
which makes all corresponding entries purple.  I think qgit should
specifically recognize entries under .git/refs/patches
and .git/refs/bases and use other ways to highlight them.  At very
least, .git/refs/patches should be ignored.  More elaborate approach
would be to draw applied StGIT patches differently, e.g. as pluses or
hollow circles.

qgit doesn't display tags except by yellow highlight.  There is no tag
name and no way to see the tag object.  I think one way to do it would
be to draw marks in the description column, like gitk does.  Another
approach would be to put all corresponding information in the patch
description pane, perhaps highlighted.

It would be great to store the cache file somewhere under .git, perhaps
under a simpler name, such as .git/qgit-cache.  The trailing ".z" should
probably be dropped, since the users are not supposed to decompress the
file or know anything about its internal structure.

Settings should be reviewed for usefulness.  Some show go to the menu,
such as "Relative time in date column".  It's hard to imagine that
somebody would use this option permanently.  "Diff against working dir"
is already in the menu, and I don't think it merits to be in the
settings.  qgit can simply remember the menu setting.  "Load file names
in background" should probably become a pair of radio buttons, since the
antithesis is not obvious.


As far as I know, qgit is unique among git front-ends for being written
in a non-interpreted language.  This fits well with git being designed
for speed.  It would be great if qgit received its fair share of
attention from GUI experts that happen to be in the git list ;-)

-- 
Regards,
Pavel Roskin

^ permalink raw reply

* [PATCH] Use symlinks with rsync in core tutorial
From: Ron Parker @ 2006-02-21  0:37 UTC (permalink / raw)
  To: git

From: Ron Parker <rdp@inthefaith.net>
Date: Mon Feb 20 18:05:39 2006 -0600

* Documentation/core-tutorial.txt: Use rsync -rl instead of -rL.

Signed-off-by: Ron Parker <rdp@inthefaith.net>


---

I think there is a typo in the core tutorial. It appears that symlinks
must be preserved when doing an rsync otherwise HEAD does not point to
the right ref, it becomes a copy of it. If I:

$ rsync -rL rsync://rsync.kernel.org/pub/scm/git/git.git/ .git

Then when IS

$ git-read-tree HEAD
fatal: Not a git repository

is what I get.  However, doing:

$ rsync -rl ...

seems to work. The only noticeable difference is that the second
creates HEAD as a symlink.

  Documentation/core-tutorial.txt |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

79ac94c0b7f8e195cf07a51c393115e7d0636069
diff --git a/Documentation/core-tutorial.txt b/Documentation/core-tutorial.txt
index 4211c81..461f845 100644
--- a/Documentation/core-tutorial.txt
+++ b/Documentation/core-tutorial.txt
@@ -724,7 +724,7 @@ create your own copy of the git reposito
 ----------------
  $ mkdir my-git
  $ cd my-git
-$ rsync -rL rsync://rsync.kernel.org/pub/scm/git/git.git/ .git
+$ rsync -rl rsync://rsync.kernel.org/pub/scm/git/git.git/ .git
 ----------------

 followed by
--
1.2.1

^ permalink raw reply related

* Re: [PATCH] Add git-annotate, a tool for assigning blame.
From: Junio C Hamano @ 2006-02-21  0:01 UTC (permalink / raw)
  To: Fredrik Kuivinen; +Cc: git, Ryan Anderson
In-Reply-To: <20060220234054.GA7903@c165.ib.student.liu.se>

Fredrik Kuivinen <freku045@student.liu.se> writes:

> I have also been working on a blame program.

Very nice to see these two.

Obviously I prefer this one for its performance ;-).

Its interface is probably friendlier when used as a preprocessor
by other tools.  I can imagine GUI source viewer that fontifies
the source code and prefers to get just the origin information
from a "blame backend".

BTW, these days I always compile things with 

	-Wall -Wdeclaration-after-statement

which caught quite a many.

Also I have my templates/hooks--pre-commit enabled so you might
want to lindent it before inclusion.

I'll play with both a bit.  Thanks for nice toys, both of you!

^ permalink raw reply

* Re: [PATCH] Makefile tweaks: Solaris 9+ dont need iconv / move up uname variables
From: Paul Jakma @ 2006-02-20 23:53 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <7v3bid8umq.fsf@assigned-by-dhcp.cox.net>

On Mon, 20 Feb 2006, Junio C Hamano wrote:

> The latter two makes sense to me.

Yeah, it's handy for installs to shared volumes, e.g. I like to set 
bindir to $(prefix)/arch/$(uname_P)/bin.

> I'd appreciate independent confirmations from people with Solaris 8 
> and/or Solaris 9 boxes.

Seems wise ;).

Note that it doesn't change things for S8. Also, I've tested this on 
Solaris 10 and fairly recent Solaris dev snapshots (snv_31). I didn't 
try a test compile on S9, however I did check on an S9u7 box and 
there is no libiconv to link against.

regards,
-- 
Paul Jakma	paul@clubi.ie	paul@jakma.org	Key ID: 64A2FF6A
Fortune:
"Ada is the work of an architect, not a computer scientist."
- Jean Icbiah, inventor of Ada, weenie

^ permalink raw reply

* Re: [PATCH] Add git-annotate, a tool for assigning blame.
From: Fredrik Kuivinen @ 2006-02-20 23:40 UTC (permalink / raw)
  To: Ryan Anderson; +Cc: Junio C Hamano, git
In-Reply-To: <11404323692193-git-send-email-ryan@michonline.com>

On Mon, Feb 20, 2006 at 05:46:09AM -0500, Ryan Anderson wrote:
> Signed-off-by: Ryan Anderson <ryan@michonline.com>
> 
> ---
> 
> (Pull from http://h4x0r5.com/~ryan/git/ryan.git/ annotate-upstream )
> 
> I'm pretty sure this version (finally) gets the edge cases correct.
> 
> I would appreciate some other testing on this, as I can't find a case
> where it falls down, but the files with a lot of history tend to have a
> lot of lines, making them hard to spotcheck without having been an
> intimate part of that history.
> 
> Oh, this is the "functional" version, but it might not qualify as "nice
> looking" yet, pleaes, feel free to complain.
> 

Nice work!


I have also been working on a blame program. The algorithm is pretty
much the one described by Junio in his blame.perl. My variant doesn't
handle renames, but it shouldn't be too hard to add that. The output
is minimal, just the line number followed by the commit SHA1.

An interesting observation is that the output from my git-blame and
your git-annotate doesn't match on all files in the git
repository. One example where several lines differ is read-cache.c. I
haven't investigated it further to find out which one is correct.


The code should be considered as a work in progress. It certainly has
a couple of rough edges. The output looks fairly sane on the few files
I have tested it on, but it wouldn't be too surprising if it gets some
cases wrong.


Signed-off-by: Fredrik Kuivinen <freku045@student.liu.se>


---

 Makefile |    2 
 blame.c  |  444 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 445 insertions(+), 1 deletions(-)
 create mode 100644 blame.c

afe1466a4f94bc72b42dce5fd1fed480c5aa4c49
diff --git a/Makefile b/Makefile
index 317be3c..4a3ffe9 100644
--- a/Makefile
+++ b/Makefile
@@ -153,7 +153,7 @@ PROGRAMS = \
 	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 git-repo-config$X git-var$X \
-	git-describe$X
+	git-describe$X git-blame$X
 
 # what 'all' will build and 'install' will install, in gitexecdir
 ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
diff --git a/blame.c b/blame.c
new file mode 100644
index 0000000..d4a2fad
--- /dev/null
+++ b/blame.c
@@ -0,0 +1,444 @@
+#include <assert.h>
+
+#include "cache.h"
+#include "refs.h"
+#include "tag.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "epoch.h"
+#include "diff.h"
+
+#define DEBUG 0
+
+struct commit** blame_lines;
+int num_blame_lines;
+
+struct util_info
+{
+    int* line_map;
+    int num_lines;
+    unsigned char sha1[20]; /* blob sha, not commit! */
+    char* buf;
+    unsigned long size;
+//    const char* path;
+};
+
+struct chunk
+{
+    int off1, len1; // ---
+    int off2, len2; // +++
+};
+
+struct patch
+{
+    struct chunk* chunks;
+    int num;
+};
+
+static void get_blob(struct commit* commit);
+
+int num_get_patch = 0;
+int num_commits = 0;
+
+struct patch* get_patch(struct commit* commit, struct commit* other)
+{
+    struct patch* ret = xmalloc(sizeof(struct patch));
+    ret->chunks = NULL;
+    ret->num = 0;
+
+    struct util_info* info_c = (struct util_info*) commit->object.util;
+    struct util_info* info_o = (struct util_info*) other->object.util;
+
+    if(!memcmp(info_c->sha1, info_o->sha1, 20))
+        return ret;
+
+    get_blob(commit);
+    get_blob(other);
+
+    FILE* fout = fopen("/tmp/git-blame-tmp1", "w");
+    if(!fout)
+        die("fopen tmp1 failed: %s", strerror(errno));
+
+    if(fwrite(info_c->buf, info_c->size, 1, fout) != 1)
+        die("fwrite 1 failed: %s", strerror(errno));
+    fclose(fout);
+
+    fout = fopen("/tmp/git-blame-tmp2", "w");
+    if(!fout)
+        die("fopen tmp2 failed: %s", strerror(errno));
+
+    if(fwrite(info_o->buf, info_o->size, 1, fout) != 1)
+        die("fwrite 2 failed: %s", strerror(errno));
+    fclose(fout);
+
+    FILE* fin = popen("diff -u0 /tmp/git-blame-tmp1 /tmp/git-blame-tmp2", "r");
+    if(!fin)
+        die("popen failed: %s", strerror(errno));
+
+    
+    char buf[1024];
+    while(fgets(buf, sizeof(buf), fin)) {
+        if(buf[0] != '@' || buf[1] != '@')
+            continue;
+
+        if(DEBUG)
+            printf("chunk line: %s", buf);
+        ret->num++;
+        ret->chunks = xrealloc(ret->chunks, sizeof(struct chunk)*ret->num);
+        struct chunk* chunk = &ret->chunks[ret->num-1];
+        
+        assert(!strncmp(buf, "@@ -", 4));
+
+        char* start = buf+4;
+        char* sp = index(start, ' ');
+        *sp = '\0';
+        if(index(start, ',')) {
+            int ret = sscanf(start, "%d,%d", &chunk->off1, &chunk->len1);
+            assert(ret == 2);  
+        } else {
+            int ret = sscanf(start, "%d", &chunk->off1);
+            assert(ret == 1);
+            chunk->len1 = 1;
+        }
+        *sp = ' ';
+
+        start = sp+1;
+        sp = index(start, ' ');
+        *sp = '\0';
+        if(index(start, ',')) {
+            int ret = sscanf(start, "%d,%d", &chunk->off2, &chunk->len2);
+            assert(ret == 2);
+        } else {
+            int ret = sscanf(start, "%d", &chunk->off2);
+            assert(ret == 1);
+            chunk->len2 = 1;
+        }
+        *sp = ' ';
+
+        if(chunk->off1 > 0)
+            chunk->off1 -= 1;
+        if(chunk->off2 > 0)
+            chunk->off2 -= 1;
+
+        assert(chunk->off1 >= 0);
+        assert(chunk->off2 >= 0);
+    }
+    fclose(fin);
+
+    num_get_patch++;
+    return ret;
+}
+
+void free_patch(struct patch* p)
+{
+    free(p->chunks);
+    free(p);
+}
+
+static int get_blob_sha1_internal(unsigned char *sha1, const char *base, int baselen,
+                                  const char *pathname, unsigned mode, int stage);
+
+
+static unsigned char blob_sha1[20];
+static int get_blob_sha1(struct tree* t, const char* pathname, unsigned char* sha1)
+{
+    const char *pathspec[2];
+    pathspec[0] = pathname;
+    pathspec[1] = NULL;
+    memset(blob_sha1, 0, sizeof(blob_sha1));
+    read_tree_recursive(t, "", 0, 0, pathspec, get_blob_sha1_internal);
+
+    int i;
+    for(i = 0; i < 20; i++) {
+        if(blob_sha1[i] != 0)
+            break;
+    }
+
+    if(i == 20)
+        return -1;
+    
+    memcpy(sha1, blob_sha1, 20);
+    return 0;
+}
+
+static int get_blob_sha1_internal(unsigned char *sha1, const char *base, int baselen,
+                                  const char *pathname, unsigned mode, int stage)
+{
+//    printf("Got blob: %s base: '%s' baselen: %d pathname: '%s' mode: %o stage: %d\n",
+//           sha1_to_hex(sha1), base, baselen, pathname, mode, stage);
+
+    if(S_ISDIR(mode))
+        return READ_TREE_RECURSIVE;
+    
+    memcpy(blob_sha1, sha1, 20);
+    return -1;
+}
+
+static void get_blob(struct commit* commit)
+{
+    struct util_info* info = commit->object.util;
+    char type[20];    
+
+    if(info->buf)
+        return;
+    
+    info->buf = read_sha1_file(info->sha1, type, &info->size);
+    assert(!strcmp(type, "blob"));
+}
+
+void print_patch(struct patch* p)
+{
+    printf("Num chunks: %d\n", p->num);
+    int i;
+    for(i = 0; i < p->num; i++) {
+        printf("%d,%d %d,%d\n", p->chunks[i].off1, p->chunks[i].len1, p->chunks[i].off2, p->chunks[i].len2);
+    }
+}
+
+
+// p is a patch from commit to other.
+void fill_line_map(struct commit* commit, struct commit* other, struct patch* p)
+{
+    int num_lines = ((struct util_info*) commit->object.util)->num_lines;
+    int* line_map = ((struct util_info*) commit->object.util)->line_map;
+    int num_lines2 = ((struct util_info*) other->object.util)->num_lines;
+    int* line_map2 = ((struct util_info*) other->object.util)->line_map;
+    int cur_chunk = 0;
+    int i1, i2;
+
+    if(p->num && DEBUG)
+        print_patch(p);
+    
+    for(i1 = 0; i1 < num_lines; i1++)
+        line_map[i1] = -1;
+
+    if(DEBUG)
+        printf("num lines 1: %d num lines 2: %d\n", num_lines, num_lines2);
+    
+    for(i1 = 0, i2 = 0; i1 < num_lines; i1++, i2++) {
+        if(DEBUG > 1)
+            printf("%d %d\n", i1, i2);
+
+        if(i2 >= num_lines2)
+            break;
+        
+        line_map[i1] = line_map2[i2];
+
+        struct chunk* chunk = NULL;
+        if(cur_chunk < p->num)
+            chunk = &p->chunks[cur_chunk];
+        
+        if(chunk && chunk->off1 == i1) {
+            i2 = chunk->off2;
+
+            if(chunk->len1 > 0)
+                i1 += chunk->len1-1;
+            if(chunk->len2 > 0)
+                i2 += chunk->len2-1;
+            cur_chunk++;
+        }
+    }
+}
+
+int map_line(struct commit* commit, int line)
+{
+    struct util_info* info = commit->object.util;
+    assert(line >= 0 && line < info->num_lines);
+    return info->line_map[line];
+}
+
+int fill_util_info(struct commit* commit, const char* path)
+{
+    if(commit->object.util)
+        return 0;
+    
+    struct util_info* util = xmalloc(sizeof(struct util_info));
+    util->buf = NULL;
+    util->size = 0;
+    util->num_lines = -1;
+    util->line_map = NULL;
+
+    commit->object.util = util;
+    
+    if(get_blob_sha1(commit->tree, path, util->sha1))
+        return -1;
+
+    return 0;
+}
+
+void alloc_line_map(struct commit* commit)
+{
+    struct util_info* util = commit->object.util;
+
+    if(util->line_map)
+        return;
+
+    get_blob(commit);
+    
+    int i;
+    util->num_lines = 0;
+    for(i = 0; i < util->size; i++) {
+        if(util->buf[i] == '\n')
+            util->num_lines++;
+    }
+    util->line_map = xmalloc(sizeof(int)*util->num_lines);
+}
+
+void copy_line_map(struct commit* dst, struct commit* src)
+{
+    struct util_info* u_dst = dst->object.util;
+    struct util_info* u_src = src->object.util;
+
+    u_dst->line_map = u_src->line_map;
+    u_dst->num_lines = u_src->num_lines;
+    u_dst->buf = u_src->buf;
+    u_dst->size = u_src->size;
+}
+    
+void process_commits(struct commit_list* list, const char* path)
+{
+    int i;
+    
+    while(list) {
+        struct commit* commit = pop_commit(&list);
+        struct commit_list* parents;
+        struct util_info* info;
+
+        info = commit->object.util;
+        num_commits++;
+        if(DEBUG)
+            printf("\nProcessing commit: %d %s\n", num_commits, sha1_to_hex(commit->object.sha1));
+        for(parents = commit->parents;
+            parents != NULL; parents = parents->next) {
+            struct commit* parent = parents->item;
+            
+            if(parse_commit(parent) < 0)
+                die("parse_commit error");
+
+            if(DEBUG)
+                printf("parent: %s\n", sha1_to_hex(parent->object.sha1));
+
+            if(fill_util_info(parent, path))
+                continue;
+
+            // Temporarily assign everything to the parent.
+            int num_blame = 0;
+            for(i = 0; i < num_blame_lines; i++) {
+                if(blame_lines[i] == commit) {
+                    num_blame++;
+                    blame_lines[i] = parent;
+                }
+            }
+
+            if(num_blame == 0)
+                continue;
+            
+            struct patch* patch = get_patch(parent, commit);
+            if(patch->num == 0) {
+                copy_line_map(parent, commit);
+            } else {
+                alloc_line_map(parent);
+                fill_line_map(parent, commit, patch);
+            }
+
+            for(i = 0; i < patch->num; i++) {
+                int l;
+                for(l = 0; l < patch->chunks[i].len2; l++) {
+                    int mapped_line = map_line(commit, patch->chunks[i].off2 + l);
+                    if(mapped_line != -1 && blame_lines[mapped_line] == parent)
+                        blame_lines[mapped_line] = commit;
+                }
+            }
+            free_patch(patch);
+        }
+    }
+}
+
+#define SEEN 1
+struct commit_list* get_commit_list(struct commit* commit, const char* pathname)
+{
+    struct commit_list* ret = NULL;
+    struct commit_list* process = NULL;
+    unsigned char sha1[20];
+    
+    commit_list_insert(commit, &process);
+
+    while(process) {
+        struct commit* com = pop_commit(&process);
+        if(com->object.flags & SEEN)
+            continue;
+
+        com->object.flags |= SEEN;
+        commit_list_insert(com, &ret);
+        struct commit_list* parents;
+
+        parse_commit(com);
+        
+        for(parents = com->parents;
+            parents != NULL; parents = parents->next) {
+            struct commit* parent = parents->item;
+
+            parse_commit(parent);
+            
+            if(!get_blob_sha1(parent->tree, pathname, sha1))
+                commit_list_insert(parent, &process);
+        }
+    }
+    
+    return ret;
+}
+
+int main(int argc, const char **argv)
+{
+    unsigned char sha1[20];
+    struct commit *commit;
+    const char* filename;
+    int i;
+    
+    setup_git_directory();
+
+    if (argc != 3)
+        die("Usage: blame commit-ish file");
+        
+    if (get_sha1(argv[1], sha1))
+        die("get_sha1 failed");
+
+    commit = lookup_commit_reference(sha1);
+
+    filename = argv[2];
+
+    struct commit_list* list = get_commit_list(commit, filename);
+    sort_in_topological_order(&list, 1);
+
+    if(fill_util_info(commit, filename)) {
+        printf("%s not found in %s\n", filename, argv[1]);
+        return 0;
+    }
+    alloc_line_map(commit);
+
+    struct util_info* util = commit->object.util;
+    num_blame_lines = util->num_lines;
+    blame_lines = xmalloc(sizeof(struct commit*)*num_blame_lines);
+
+
+    for(i = 0; i < num_blame_lines; i++) {
+        blame_lines[i] = commit;
+
+        ((struct util_info*) commit->object.util)->line_map[i] = i;
+    }    
+    
+    process_commits(list, filename);
+
+    for(i = 0; i < num_blame_lines; i++) {
+        printf("%d %s\n", i+1-1, sha1_to_hex(blame_lines[i]->object.sha1));
+//        printf("%d %s\n", i+1-1, find_unique_abbrev(blame_lines[i]->object.sha1, 6));
+    }
+    
+    if(DEBUG) {
+        printf("num get patch: %d\n", num_get_patch);
+        printf("num commits: %d\n", num_commits);
+    }
+
+    return 0;
+}
-- 
1.2.1.g62a4-dirty

^ permalink raw reply related

* [PATCH] Makefile tweaks: Solaris 9+ dont need iconv / move up uname variables
From: Paul Jakma @ 2006-02-20 23:36 UTC (permalink / raw)
  To: git list; +Cc: Junio C Hamano

- Solaris 9 and up do not need -liconv, so NEEDS_LIBICONV should be set
   only for S8.
- Move the declaration of the uname variables to early in the Makefile
   so they can be referenced by prefix and gitexecdir variables.
- gitexecdir defaults to being same as bindir, it might as well reference
   that variable.

Signed-off-by: Paul Jakma <paul@quagga.net>

---

  Makefile |   15 ++++++++-------
  1 files changed, 8 insertions(+), 7 deletions(-)

6ee3566e9b5408b7bfedbb6ffcdbac1be6bafbbb
diff --git a/Makefile b/Makefile
index 317be3c..a225e9c 100644
--- a/Makefile
+++ b/Makefile
@@ -70,7 +70,12 @@ all:

  GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
  	@$(SHELL_PATH) ./GIT-VERSION-GEN
--include GIT-VERSION-FILE
+
+uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
+uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not')
+uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not')
+uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not')
+uname_P := $(shell sh -c 'uname -p 2>/dev/null || echo not')

  # CFLAGS and LDFLAGS are for the users to override from the command line.

@@ -82,7 +87,7 @@ STRIP ?= strip

  prefix = $(HOME)
  bindir = $(prefix)/bin
-gitexecdir = $(prefix)/bin
+gitexecdir = $(bindir)
  template_dir = $(prefix)/share/git-core/templates/
  GIT_PYTHON_DIR = $(prefix)/share/git-core/python
  # DESTDIR=
@@ -212,10 +217,6 @@ shellquote = '$(call shq,$(1))'
  # We choose to avoid "if .. else if .. else .. endif endif"
  # because maintaining the nesting to match is a pain.  If
  # we had "elif" things would have been much nicer...
-uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
-uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not')
-uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not')
-uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not')

  ifeq ($(uname_S),Darwin)
  	NEEDS_SSL_WITH_CRYPTO = YesPlease
@@ -230,10 +231,10 @@ endif
  ifeq ($(uname_S),SunOS)
  	NEEDS_SOCKET = YesPlease
  	NEEDS_NSL = YesPlease
-	NEEDS_LIBICONV = YesPlease
  	SHELL_PATH = /bin/bash
  	NO_STRCASESTR = YesPlease
  	ifeq ($(uname_R),5.8)
+		NEEDS_LIBICONV = YesPlease
  		NO_UNSETENV = YesPlease
  		NO_SETENV = YesPlease
  	endif
-- 
1.2.1

^ permalink raw reply related

* Re: [PATCH] Add git-annotate, a tool for assigning blame.
From: Junio C Hamano @ 2006-02-20 22:54 UTC (permalink / raw)
  To: Ryan Anderson; +Cc: git
In-Reply-To: <11404323692193-git-send-email-ryan@michonline.com>

Ryan Anderson <ryan@michonline.com> writes:

> I would appreciate some other testing on this, as I can't find a case
> where it falls down, but the files with a lot of history tend to have a
> lot of lines, making them hard to spotcheck without having been an
> intimate part of that history.

I looked at a couple of files, including pack-objects.c,
rev-list.c (in "pu"), and svnimport.perl; what I saw made sense.
I think however we would want to check things with a lot of
merges, so git repository may not be a good guinea pig.

> Oh, this is the "functional" version, but it might not qualify as "nice
> looking" yet, pleaes, feel free to complain.

Nice.

Two design glitches and an implementation:

 - You seem to rely on the working tree file to be clean relative to
   HEAD.

 - You do not take anything other than HEAD as the starting point.

Maybe an additional -r<this-version> option which defaults to
HEAD, plus reading the blob always from that tree as the
starting point would be helpful.

> +	open(P,"-|","git-rev-list","--parents","--remove-empty",$rev,"--",$file)

Johannes noticed I slipped this form which Perl 5.6 does not
grok in another program.  Eric pointed out he uses a backward
compatible idiom in his code.

We would need to make this one safe; something like this, perhaps:

	sub open_read_pipe {
        	my ($fh, @cmd) = @_;
                my $pid = open($fh, '-|');
		die "$!" unless defined($pid);
		if (!$pid) {
			exec(@cmd) or die "$!";
		}
		return $fh;
	}
	...
        open_read_pipe(\*P, qw(git-rev-list --parents --remove-empty),
			$rev, '--', $file);


> +	open(P,"-|","git-diff-tree", "-M50", "-r","--name-status", "-z","$rev")
> +		or die "Failed to exec git-diff: $!";

If you do not mean "I want -M50" but you meant "I want whatever
is default", I'd leave that 50 out.

It is probably premature to talk about issues for UI that can be
built on top of this, but what I found interesting was this.
While looking at the annotate output, whenever I got curious
about one line ("Oh, this is an ancient change and by somebody
who does not feed patches to git regularly -- what was the
change about???"), I grabbed the SHA1 of the commit on the line
and threw it at "git show".  It was a good way to see how the
change to the line was done in context.  Maybe a two-pane UI
that shows annotate on the top pane, and activating one line
from it shows "git show" output on the bottom pane to show the
commit log plus changes the commit introduced to the file and
other files.

^ permalink raw reply

* Re: [PATCH 2/2] Add 'stg uncommit' command
From: Catalin Marinas @ 2006-02-20 22:49 UTC (permalink / raw)
  To: Karl Hasselström; +Cc: git
In-Reply-To: <20060220173048.GC23501@diana.vm.bytemark.co.uk>

On 20/02/06, Karl Hasselström <kha@treskal.com> wrote:
> On 2006-02-20 17:20:47 +0000, Catalin Marinas wrote:
> > I fixed the escaping in the name_email* functions (I'll push it
> > tonight). It was adding a \ for every character it didn't know. It
> > now only escapes the quotes and back-slashes. This is needed when
> > passing the strings via the GIT_AUTHOR_* variables.
>
> It put curly braces around the name as well.

It wasn't StGIT. Running "git log" on my machine only shows \'s and
some weird characters. Maybe it's your terminal showing braces.

--
Catalin

^ permalink raw reply

* [PATCH] cvsimport: avoid open "-|" list form for Perl 5.6
From: Junio C Hamano @ 2006-02-20 22:19 UTC (permalink / raw)
  To: git; +Cc: Eric Wong, Matthias Urlichs, Martin Langhoff
In-Reply-To: <7vr75xbs8w.fsf_-_@assigned-by-dhcp.cox.net>


Signed-off-by: Junio C Hamano <junkio@cox.net>

---

 * Fifth of the four patch series.  I cannot count ;-).

 git-cvsimport.perl |    6 +++++-
 1 files changed, 5 insertions(+), 1 deletions(-)

eb815c1bb8a40ae18d80e99f8547137ea05318bf
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
index 24f9834..b46469a 100755
--- a/git-cvsimport.perl
+++ b/git-cvsimport.perl
@@ -846,8 +846,12 @@ while(<CVS>) {
 			print "Drop $fn\n" if $opt_v;
 		} else {
 			print "".($init ? "New" : "Update")." $fn: $size bytes\n" if $opt_v;
-			open my $F, '-|', "git-hash-object -w $tmpname"
+			my $pid = open(my $F, '-|');
+			die $! unless defined $pid;
+			if (!$pid) {
+			    exec("git-hash-object", "-w", $tmpname)
 				or die "Cannot create object: $!\n";
+			}
 			my $sha = <$F>;
 			chomp $sha;
 			close $F;
-- 
1.2.2.g5be4ea

^ permalink raw reply related

* [PATCH] send-email: avoid open "-|" list form for Perl 5.6
From: Junio C Hamano @ 2006-02-20 22:12 UTC (permalink / raw)
  To: git; +Cc: Eric Wong, Ryan Anderson
In-Reply-To: <7vr75xbs8w.fsf_-_@assigned-by-dhcp.cox.net>


Signed-off-by: Junio C Hamano <junkio@cox.net>

---

 git-send-email.perl |   39 ++++++++++++++++++++++-----------------
 1 files changed, 22 insertions(+), 17 deletions(-)

044ece3bc8bde227babd2f710f8216f2cb631034
diff --git a/git-send-email.perl b/git-send-email.perl
index 13b85dd..b4f04f9 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -59,24 +59,29 @@ my $rc = GetOptions("from=s" => \$from,
 
 # Now, let's fill any that aren't set in with defaults:
 
-open(GITVAR,"-|","git-var","-l")
-	or die "Failed to open pipe from git-var: $!";
-
-my ($author,$committer);
-while(<GITVAR>) {
-	chomp;
-	my ($var,$data) = split /=/,$_,2;
-	my @fields = split /\s+/, $data;
-
-	my $ident = join(" ", @fields[0...(@fields-3)]);
-
-	if ($var eq 'GIT_AUTHOR_IDENT') {
-		$author = $ident;
-	} elsif ($var eq 'GIT_COMMITTER_IDENT') {
-		$committer = $ident;
-	}
+sub gitvar {
+    my ($var) = @_;
+    my $fh;
+    my $pid = open($fh, '-|');
+    die "$!" unless defined $pid;
+    if (!$pid) {
+	exec('git-var', $var) or die "$!";
+    }
+    my ($val) = <$fh>;
+    close $fh or die "$!";
+    chomp($val);
+    return $val;
+}
+
+sub gitvar_ident {
+    my ($name) = @_;
+    my $val = gitvar($name);
+    my @field = split(/\s+/, $val);
+    return join(' ', @field[0...(@field-3)]);
 }
-close(GITVAR);
+
+$author = gitvar_ident('GIT_AUTHOR_IDENT');
+$committer = gitvar_ident('GIT_COMMITTER_IDENT');
 
 my $prompting = 0;
 if (!defined $from) {
-- 
1.2.2.g5be4ea

^ permalink raw reply related

* [PATCH] svmimport: avoid open "-|" list form for Perl 5.6
From: Junio C Hamano @ 2006-02-20 22:12 UTC (permalink / raw)
  To: git; +Cc: Eric Wong, Matthias Urlichs
In-Reply-To: <7vr75xbs8w.fsf_-_@assigned-by-dhcp.cox.net>


Signed-off-by: Junio C Hamano <junkio@cox.net>

---

 git-svnimport.perl |   20 ++++++++++++++++----
 1 files changed, 16 insertions(+), 4 deletions(-)

f7e8f8415c5c88082daecac44cfbba561113a3d9
diff --git a/git-svnimport.perl b/git-svnimport.perl
index c536d70..ee2940f 100755
--- a/git-svnimport.perl
+++ b/git-svnimport.perl
@@ -10,7 +10,6 @@
 # The head revision is on branch "origin" by default.
 # You can change that with the '-o' option.
 
-require 5.008; # for shell-safe open("-|",LIST)
 use strict;
 use warnings;
 use Getopt::Std;
@@ -322,8 +321,12 @@ sub get_file($$$) {
 		return undef unless defined $name;
 	}
 
-	open my $F, '-|', "git-hash-object", "-w", $name
+	my $pid = open(my $F, '-|');
+	die $! unless defined $pid;
+	if (!$pid) {
+	    exec("git-hash-object", "-w", $name)
 		or die "Cannot create object: $!\n";
+	}
 	my $sha = <$F>;
 	chomp $sha;
 	close $F;
@@ -398,7 +401,12 @@ sub copy_path($$$$$$$$) {
 			$srcpath =~ s#/*$#/#;
 	}
 	
-	open my $f,"-|","git-ls-tree","-r","-z",$gitrev,$srcpath;
+	my $pid = open my $f,'-|';
+	die $! unless defined $pid;
+	if (!$pid) {
+		exec("git-ls-tree","-r","-z",$gitrev,$srcpath)
+			or die $!;
+	}
 	local $/ = "\0";
 	while(<$f>) {
 		chomp;
@@ -554,7 +562,11 @@ sub commit {
 				@o1 = @old;
 				@old = ();
 			}
-			open my $F, "-|", "git-ls-files", "-z", @o1 or die $!;
+			my $pid = open my $F, "-|";
+			die "$!" unless defined $pid;
+			if (!$pid) {
+				exec("git-ls-files", "-z", @o1) or die $!;
+			}
 			@o1 = ();
 			local $/ = "\0";
 			while(<$F>) {
-- 
1.2.2.g5be4ea

^ permalink raw reply related

* [PATCH] rerere: avoid open "-|" list form for Perl 5.6
From: Junio C Hamano @ 2006-02-20 22:12 UTC (permalink / raw)
  To: git; +Cc: Eric Wong
In-Reply-To: <7vr75xbs8w.fsf_-_@assigned-by-dhcp.cox.net>


Signed-off-by: Junio C Hamano <junkio@cox.net>

---

 git-rerere.perl |    6 +++++-
 1 files changed, 5 insertions(+), 1 deletions(-)

46fa107ab91b25eb928a9945ce4e9143b9c36df3
diff --git a/git-rerere.perl b/git-rerere.perl
index df11951..d3664ff 100755
--- a/git-rerere.perl
+++ b/git-rerere.perl
@@ -131,7 +131,11 @@ sub record_preimage {
 sub find_conflict {
 	my $in;
 	local $/ = "\0";
-	open $in, '-|', qw(git ls-files -z -u) or die "$!: ls-files";
+	my $pid = open($in, '-|');
+	die "$!" unless defined $pid;
+	if (!$pid) {
+		exec(qw(git ls-files -z -u)) or die "$!: ls-files";
+	}
 	my %path = ();
 	my @path = ();
 	while (<$in>) {
-- 
1.2.2.g5be4ea

^ permalink raw reply related

* [PATCH] fmt-merge-msg: avoid open "-|" list form for Perl 5.6
From: Junio C Hamano @ 2006-02-20 22:05 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Eric Wong, git
In-Reply-To: <20060220191011.GA18085@hand.yhbt.net>


Signed-off-by: Junio C Hamano <junkio@cox.net>

---

 * Eric, thanks for the hint.  I have this four-patch series.
   Could people with perl 5.6 please check them?

 git-fmt-merge-msg.perl |   24 ++++++++++++++++--------
 1 files changed, 16 insertions(+), 8 deletions(-)

615782c9609bf23be55b403e994d88c1047be996
diff --git a/git-fmt-merge-msg.perl b/git-fmt-merge-msg.perl
index c34ddc5..a77e94e 100755
--- a/git-fmt-merge-msg.perl
+++ b/git-fmt-merge-msg.perl
@@ -28,11 +28,12 @@ sub andjoin {
 }
 
 sub repoconfig {
-	my $fh;
 	my $val;
 	eval {
-		open $fh, '-|', 'git-repo-config', '--get', 'merge.summary'
-		    or die "$!";
+		my $pid = open(my $fh, '-|');
+		if (!$pid) {
+			exec('git-repo-config', '--get', 'merge.summary');
+		}
 		($val) = <$fh>;
 		close $fh;
 	};
@@ -41,25 +42,32 @@ sub repoconfig {
 
 sub current_branch {
 	my $fh;
-	open $fh, '-|', 'git-symbolic-ref', 'HEAD' or die "$!";
+	my $pid = open($fh, '-|');
+	die "$!" unless defined $pid;
+	if (!$pid) {
+	    exec('git-symbolic-ref', 'HEAD') or die "$!";
+	}
 	my ($bra) = <$fh>;
 	chomp($bra);
+	close $fh or die "$!";
 	$bra =~ s|^refs/heads/||;
 	if ($bra ne 'master') {
 		$bra = " into $bra";
 	} else {
 		$bra = "";
 	}
-
 	return $bra;
 }
 
 sub shortlog {
 	my ($tip, $limit) = @_;
 	my ($fh, @result);
-	open $fh, '-|', ('git-log', "--max-count=$limit", '--topo-order',
-			 '--pretty=oneline', $tip, '^HEAD')
-	    or die "$!";
+	my $pid = open($fh, '-|');
+	die "$!" unless defined $pid;
+	if (!$pid) {
+	    exec('git-log', "--max-count=$limit", '--topo-order',
+		 '--pretty=oneline', $tip, '^HEAD') or die "$!";
+	}
 	while (<$fh>) {
 		s/^[0-9a-f]{40}\s+//;
 		push @result, $_;
-- 
1.2.2.g5be4ea

^ permalink raw reply related

* Re: Should we support Perl 5.6?
From: Junio C Hamano @ 2006-02-20 21:15 UTC (permalink / raw)
  To: Andreas Ericsson; +Cc: Johannes Schindelin, git
In-Reply-To: <43FA2E46.2000503@op5.se>

Andreas Ericsson <ae@op5.se> writes:

> I think we agreed not to bother at all with Perl 5.4 and earlier, and
> not to bend over backwards to support 5.6. This seems like a simple
> fix though, so I'm sure Junio will accept a patch.

Correct.  I wasn't being careful enough.

^ permalink raw reply

* Re: Should we support Perl 5.6?
From: Andreas Ericsson @ 2006-02-20 21:01 UTC (permalink / raw)
  To: Eric Wong; +Cc: Johannes Schindelin, git
In-Reply-To: <20060220191011.GA18085@hand.yhbt.net>

Eric Wong wrote:
> Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:
> 
>>Hi,
>>
>>I just had a failure when pulling, because since a few days (to be exact, 
>>since commit 1cb30387, git-fmt-merge-msg uses a syntax which is not 
>>understood by Perl 5.6.
>>
>>It is this:
>>
>>	open $fh, '-|', 'git-symbolic-ref', 'HEAD' or die "$!";
> 
> 
> This is just 5.8 shorthand for the following (which is 5.6-compatible,
> and probably for earlier versions, too):
> 
> 	my $pid = open my $fh, '-|';
> 	defined $pid or die "Unable to fork: $!\n";
> 	if ($pid == 0) {
> 		exec 'git-symbolic-ref', 'HEAD' or die "$!";
> 	}
> 	<continue with original code here>
> 
> All of the Perl code I've written uses this method.
> 
> 
>>I know that there was already some discussion on this list, but I don't 
>>remember if we decided on leaving 5.6 behind or not.
>>
>>Somebody remembers?
> 
> 
> IIRC, there was no clear decision.
> 

I think we agreed not to bother at all with Perl 5.4 and earlier, and 
not to bend over backwards to support 5.6. This seems like a simple fix 
though, so I'm sure Junio will accept a patch.

-- 
Andreas Ericsson                   andreas.ericsson@op5.se
OP5 AB                             www.op5.se
Tel: +46 8-230225                  Fax: +46 8-230231

^ permalink raw reply

* Re: git-svnimport -- http/dav authentication notes
From: Eric Wong @ 2006-02-20 19:22 UTC (permalink / raw)
  To: Martin Langhoff; +Cc: Git Mailing List
In-Reply-To: <46a038f90602192324v7193f154od4ff6952a68c799d@mail.gmail.com>

Martin Langhoff <martin.langhoff@gmail.com> wrote:
> After a lot of trial and error against a server I don't control (so I
> can't log things on) I've managed to use svn-import on it and it
> mostly seems to work. Kind of. It's slowly grinding through the
> commits.
> 
> To make it short, I seem to be doing well with:
> 
>    git-svnimport -t tags -T trunk -b branches -o svntrunk -v
>          'http://martin%40catalyst.net.nz:password@nameless.server.org/svn/someproject'
> 
> The repo has LDAP authentication, and the username is my email
> address, so I guessed that url-encoding would work, and it did.

Logging in with the svn command-line client (svn ls <url>) be enough to
cache auth information for the SVN library (and therefore svnimport)?
Unless you've disabled it in your config, of course...

> svnimport is a bit of a mistery actually. I often know if it's really
> doing the import or the files are going to be there but empty. SVN is
> so flexible in its "everything is a directory" way of thinking that if
> I mess up -t or -b I get the commits alright... but no files.
> 
> Strange world, svn ;-)

Hear, hear.  I always had to reread the manpage or look here for
examples on how to use it.  And then echo !! > .git/info/svnimport-cmd
after I was done using it so I wouldn't have to remember which command
switches I used.

Then I decided to write git-svn :>

-- 
Eric Wong

^ permalink raw reply

* Re: Should we support Perl 5.6?
From: Eric Wong @ 2006-02-20 19:10 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git
In-Reply-To: <Pine.LNX.4.63.0602201934270.28957@wbgn013.biozentrum.uni-wuerzburg.de>

Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:
> Hi,
> 
> I just had a failure when pulling, because since a few days (to be exact, 
> since commit 1cb30387, git-fmt-merge-msg uses a syntax which is not 
> understood by Perl 5.6.
> 
> It is this:
> 
> 	open $fh, '-|', 'git-symbolic-ref', 'HEAD' or die "$!";

This is just 5.8 shorthand for the following (which is 5.6-compatible,
and probably for earlier versions, too):

	my $pid = open my $fh, '-|';
	defined $pid or die "Unable to fork: $!\n";
	if ($pid == 0) {
		exec 'git-symbolic-ref', 'HEAD' or die "$!";
	}
	<continue with original code here>

All of the Perl code I've written uses this method.

> I know that there was already some discussion on this list, but I don't 
> remember if we decided on leaving 5.6 behind or not.
> 
> Somebody remembers?

IIRC, there was no clear decision.

I still have some Debian Woody machines/chroots with 5.6 around in some
places.  I don't use git on them, but I may someday, but upgrading to
Sarge is more likely on those.

-- 
Eric Wong

^ permalink raw reply

* [PATCH 8/9] contrib/git-svn: add Makefile, test, and associated ignores
From: Eric Wong @ 2006-02-20 18:57 UTC (permalink / raw)
  To: git; +Cc: junkio, Eric Wong
In-Reply-To: <11404618483587-git-send-email-normalperson@yhbt.net>

Signed-off-by: Eric Wong <normalperson@yhbt.net>

---

 contrib/git-svn/.gitignore                 |    4 +
 contrib/git-svn/Makefile                   |   32 ++++
 contrib/git-svn/git-svn.perl               |    0 
 contrib/git-svn/t/t0000-contrib-git-svn.sh |  216 ++++++++++++++++++++++++++++
 4 files changed, 252 insertions(+), 0 deletions(-)
 create mode 100644 contrib/git-svn/.gitignore
 create mode 100644 contrib/git-svn/Makefile
 rename contrib/git-svn/{git-svn => git-svn.perl} (100%)
 create mode 100644 contrib/git-svn/t/t0000-contrib-git-svn.sh

1f5de0dcbe26fedb3236c437808d2cb20ab282c1
diff --git a/contrib/git-svn/.gitignore b/contrib/git-svn/.gitignore
new file mode 100644
index 0000000..d8d87e3
--- /dev/null
+++ b/contrib/git-svn/.gitignore
@@ -0,0 +1,4 @@
+git-svn
+git-svn.xml
+git-svn.html
+git-svn.1
diff --git a/contrib/git-svn/Makefile b/contrib/git-svn/Makefile
new file mode 100644
index 0000000..a330c61
--- /dev/null
+++ b/contrib/git-svn/Makefile
@@ -0,0 +1,32 @@
+all: git-svn
+
+prefix?=$(HOME)
+bindir=$(prefix)/bin
+mandir=$(prefix)/man
+man1=$(mandir)/man1
+INSTALL?=install
+doc_conf=../../Documentation/asciidoc.conf
+-include ../../config.mak
+
+git-svn: git-svn.perl
+	cp $< $@
+	chmod +x $@
+
+install: all
+	$(INSTALL) -d -m755 $(DESTDIR)$(bindir)
+	$(INSTALL) git-svn $(DESTDIR)$(bindir)
+
+install-doc: doc
+	$(INSTALL) git-svn.1 $(DESTDIR)$(man1)
+
+doc: git-svn.1
+git-svn.1 : git-svn.xml
+	xmlto man git-svn.xml
+git-svn.xml : git-svn.txt
+	asciidoc -b docbook -d manpage \
+		-f ../../Documentation/asciidoc.conf $<
+test:
+	cd t && $(SHELL) ./t0000-contrib-git-svn.sh
+
+clean:
+	rm -f git-svn *.xml *.html *.1
diff --git a/contrib/git-svn/git-svn b/contrib/git-svn/git-svn.perl
similarity index 100%
rename from contrib/git-svn/git-svn
rename to contrib/git-svn/git-svn.perl
diff --git a/contrib/git-svn/t/t0000-contrib-git-svn.sh b/contrib/git-svn/t/t0000-contrib-git-svn.sh
new file mode 100644
index 0000000..181dfe0
--- /dev/null
+++ b/contrib/git-svn/t/t0000-contrib-git-svn.sh
@@ -0,0 +1,216 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Eric Wong
+#
+
+
+PATH=$PWD/../:$PATH
+test_description='git-svn tests'
+if test -d ../../../t
+then
+    cd ../../../t
+else
+    echo "Must be run in contrib/git-svn/t" >&2
+    exit 1
+fi
+
+. ./test-lib.sh
+
+GIT_DIR=$PWD/.git
+GIT_SVN_DIR=$GIT_DIR/git-svn
+SVN_TREE=$GIT_SVN_DIR/tree
+
+svnadmin >/dev/null 2>&1
+if test $? != 1
+then
+    test_expect_success 'skipping contrib/git-svn test' :
+    test_done
+    exit
+fi
+
+svn >/dev/null 2>&1
+if test $? != 1
+then
+    test_expect_success 'skipping contrib/git-svn test' :
+    test_done
+    exit
+fi
+
+svnrepo=$PWD/svnrepo
+
+set -e
+
+svnadmin create $svnrepo
+svnrepo="file://$svnrepo/test-git-svn"
+
+mkdir import
+
+cd import
+
+echo foo > foo
+ln -s foo foo.link
+mkdir -p dir/a/b/c/d/e
+echo 'deep dir' > dir/a/b/c/d/e/file
+mkdir -p bar
+echo 'zzz' > bar/zzz
+echo '#!/bin/sh' > exec.sh
+chmod +x exec.sh
+svn import -m 'import for git-svn' . $svnrepo >/dev/null
+
+cd ..
+
+rm -rf import
+
+test_expect_success \
+    'initialize git-svn' \
+    "git-svn init $svnrepo"
+
+test_expect_success \
+    'import an SVN revision into git' \
+    'git-svn fetch'
+
+
+name='try a deep --rmdir with a commit'
+git checkout -b mybranch git-svn-HEAD
+mv dir/a/b/c/d/e/file dir/file
+cp dir/file file
+git update-index --add --remove dir/a/b/c/d/e/file dir/file file
+git commit -m "$name"
+
+test_expect_success "$name" \
+    "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch &&
+     test -d $SVN_TREE/dir && test ! -d $SVN_TREE/dir/a"
+
+
+name='detect node change from file to directory #1'
+mkdir dir/new_file
+mv dir/file dir/new_file/file
+mv dir/new_file dir/file
+git update-index --remove dir/file
+git update-index --add dir/file/file
+git commit -m "$name"
+
+test_expect_code 1 "$name" \
+    'git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch' \
+    || true
+
+
+name='detect node change from directory to file #1'
+rm -rf dir $GIT_DIR/index
+git checkout -b mybranch2 git-svn-HEAD
+mv bar/zzz zzz
+rm -rf bar
+mv zzz bar
+git update-index --remove -- bar/zzz
+git update-index --add -- bar
+git commit -m "$name"
+
+test_expect_code 1 "$name" \
+    'git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch2' \
+    || true
+
+
+name='detect node change from file to directory #2'
+rm -f $GIT_DIR/index
+git checkout -b mybranch3 git-svn-HEAD
+rm bar/zzz
+git-update-index --remove bar/zzz
+mkdir bar/zzz
+echo yyy > bar/zzz/yyy
+git-update-index --add bar/zzz/yyy
+git commit -m "$name"
+
+test_expect_code 1 "$name" \
+    'git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch3' \
+    || true
+
+
+name='detect node change from directory to file #2'
+rm -f $GIT_DIR/index
+git checkout -b mybranch4 git-svn-HEAD
+rm -rf dir
+git update-index --remove -- dir/file
+touch dir
+echo asdf > dir
+git update-index --add -- dir
+git commit -m "$name"
+
+test_expect_code 1 "$name" \
+    'git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch4' \
+    || true
+
+
+name='remove executable bit from a file'
+rm -f $GIT_DIR/index
+git checkout -b mybranch5 git-svn-HEAD
+chmod -x exec.sh
+git update-index exec.sh
+git commit -m "$name"
+
+test_expect_success "$name" \
+    "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch5 &&
+     test ! -x $SVN_TREE/exec.sh"
+
+
+name='add executable bit back file'
+chmod +x exec.sh
+git update-index exec.sh
+git commit -m "$name"
+
+test_expect_success "$name" \
+    "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch5 &&
+     test -x $SVN_TREE/exec.sh"
+
+
+
+name='executable file becomes a symlink to bar/zzz (file)'
+rm exec.sh
+ln -s bar/zzz exec.sh
+git update-index exec.sh
+git commit -m "$name"
+
+test_expect_success "$name" \
+    "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch5 &&
+     test -L $SVN_TREE/exec.sh"
+
+
+
+name='new symlink is added to a file that was also just made executable'
+chmod +x bar/zzz
+ln -s bar/zzz exec-2.sh
+git update-index --add bar/zzz exec-2.sh
+git commit -m "$name"
+
+test_expect_success "$name" \
+    "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch5 &&
+     test -x $SVN_TREE/bar/zzz &&
+     test -L $SVN_TREE/exec-2.sh"
+
+
+
+name='modify a symlink to become a file'
+git help > help || true
+rm exec-2.sh
+cp help exec-2.sh
+git update-index exec-2.sh
+git commit -m "$name"
+
+test_expect_success "$name" \
+    "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch5 &&
+     test -f $SVN_TREE/exec-2.sh &&
+     test ! -L $SVN_TREE/exec-2.sh &&
+     diff -u help $SVN_TREE/exec-2.sh"
+
+
+
+name='test fetch functionality (svn => git) with alternate GIT_SVN_ID'
+GIT_SVN_ID=alt
+export GIT_SVN_ID
+test_expect_success "$name" \
+    "git-svn init $svnrepo && git-svn fetch -v &&
+     git-rev-list --pretty=raw git-svn-HEAD | grep ^tree | uniq > a &&
+     git-rev-list --pretty=raw alt-HEAD | grep ^tree | uniq > b &&
+     diff -u a b"
+
+test_done
+
-- 
1.2.0.gdee6

^ permalink raw reply related

* [PATCH 9/9] git-svn: 0.9.1: add --version and copyright/license (GPL v2+) information
From: Eric Wong @ 2006-02-20 18:57 UTC (permalink / raw)
  To: git; +Cc: junkio, Eric Wong
In-Reply-To: <11404618491275-git-send-email-normalperson@yhbt.net>

Signed-off-by: Eric Wong <normalperson@yhbt.net>

---

 contrib/git-svn/git-svn.perl |   13 +++++++++++--
 1 files changed, 11 insertions(+), 2 deletions(-)

372473c658e9f48323430af003f8a1a887989edf
diff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl
index 3a59454..a32ce15 100755
--- a/contrib/git-svn/git-svn.perl
+++ b/contrib/git-svn/git-svn.perl
@@ -1,4 +1,6 @@
 #!/usr/bin/env perl
+# Copyright (C) 2006, Eric Wong <normalperson@yhbt.net>
+# License: GPL v2 or later
 use warnings;
 use strict;
 use vars qw/	$AUTHOR $VERSION
@@ -6,7 +8,7 @@ use vars qw/	$AUTHOR $VERSION
 		$GIT_SVN_INDEX $GIT_SVN
 		$GIT_DIR $REV_DIR/;
 $AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
-$VERSION = '0.9.0';
+$VERSION = '0.9.1';
 $GIT_DIR = $ENV{GIT_DIR} || "$ENV{PWD}/.git";
 $GIT_SVN = $ENV{GIT_SVN_ID} || 'git-svn';
 $GIT_SVN_INDEX = "$GIT_DIR/$GIT_SVN/index";
@@ -31,7 +33,7 @@ use File::Spec qw//;
 my $sha1 = qr/[a-f\d]{40}/;
 my $sha1_short = qr/[a-f\d]{6,40}/;
 my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,
-	$_find_copies_harder, $_l);
+	$_find_copies_harder, $_l, $_version);
 
 GetOptions(	'revision|r=s' => \$_revision,
 		'no-ignore-externals' => \$_no_ignore_ext,
@@ -41,6 +43,7 @@ GetOptions(	'revision|r=s' => \$_revisio
 		'help|H|h' => \$_help,
 		'find-copies-harder' => \$_find_copies_harder,
 		'l=i' => \$_l,
+		'version|V' => \$_version,
 		'no-stop-on-copy' => \$_no_stop_copy );
 my %cmd = (
 	fetch => [ \&fetch, "Download new revisions from SVN" ],
@@ -66,6 +69,7 @@ foreach (keys %cmd) {
 	}
 }
 usage(0) if $_help;
+version() if $_version;
 usage(1) unless (defined $cmd);
 svn_check_ignore_externals();
 $cmd{$cmd}->[0]->(@ARGV);
@@ -91,6 +95,11 @@ and want to keep them separate.
 	exit $exit;
 }
 
+sub version {
+	print "git-svn version $VERSION\n";
+	exit 0;
+}
+
 sub rebuild {
 	$SVN_URL = shift or undef;
 	my $repo_uuid;
-- 
1.2.0.gdee6

^ permalink raw reply related

* [PATCH 7/9] git-svn: fix several corner-case and rare bugs with 'commit'
From: Eric Wong @ 2006-02-20 18:57 UTC (permalink / raw)
  To: git; +Cc: junkio, Eric Wong
In-Reply-To: <11404618483094-git-send-email-normalperson@yhbt.net>

None of these were really show-stoppers (or even triggered)
on most of the trees I've tracked.

* Node change prevention for identically named nodes.  This is
  a limitation of SVN, but we find the error and exit before
  it's passed to SVN so we don't dirty our working tree when our
  commit fails.  git-svn will exit with an error code 1 if any
  of the following conditions are found:

  1.  a directory is removed and a file of the same name of the
      removed directory is created
  1a. a file has its parent directory removed and the file is
      takes the name of the removed parent directory::
          baz/zzz    =>  baz
  2.  a file is removed and a directory of the same name of the
      removed file is created.
  2a. a file is moved into a deeper directory that shares the
      previous name of the file::
          dir/$file  =>  dir/file/$file

  Since SVN cannot handle these cases, the user will have to
  manually split the commit into several parts.

* --rmdir now handles nested/deep removals. If dir/a/b/c/d/e/file
  is removed, and everything else is in the dir/ hierarchy is
  otherwise empty, then dir/ will be deleted when file is deleted
  from svn and --rmdir specified.

* Always assert that we have written the tree we want to write
  on commits.  This helped me find several bugs in the symlink
  handling code (which as been fixed).

* Several symlink handling fixes.  We now refuse to set
  permissions on symlinks.  We also always unlink a file
  if we're going to overwrite it.

* Apply changes in a pre-determined order, so we always have
  rename from locations handy before we delete them.

Signed-off-by: Eric Wong <normalperson@yhbt.net>

---

 contrib/git-svn/git-svn |  260 ++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 200 insertions(+), 60 deletions(-)

cc9e5f27fee073de35fd6f50ec15ea59dda8b71b
diff --git a/contrib/git-svn/git-svn b/contrib/git-svn/git-svn
index 25c248d..3a59454 100755
--- a/contrib/git-svn/git-svn
+++ b/contrib/git-svn/git-svn
@@ -238,7 +238,11 @@ sub commit {
 	my $svn_current_rev =  svn_info('.')->{'Last Changed Rev'};
 	foreach my $c (@revs) {
 		print "Committing $c\n";
-		svn_checkout_tree($svn_current_rev, $c);
+		my $mods = svn_checkout_tree($svn_current_rev, $c);
+		if (scalar @$mods == 0) {
+			print "Skipping, no changes detected\n";
+			next;
+		}
 		$svn_current_rev = svn_commit_tree($svn_current_rev, $c);
 	}
 	print "Done committing ",scalar @revs," revisions to SVN\n";
@@ -267,9 +271,9 @@ sub setup_git_svn {
 }
 
 sub assert_svn_wc_clean {
-	my ($svn_rev, $commit) = @_;
+	my ($svn_rev, $treeish) = @_;
 	croak "$svn_rev is not an integer!\n" unless ($svn_rev =~ /^\d+$/);
-	croak "$commit is not a sha1!\n" unless ($commit =~ /^$sha1$/o);
+	croak "$treeish is not a sha1!\n" unless ($treeish =~ /^$sha1$/o);
 	my $svn_info = svn_info('.');
 	if ($svn_rev != $svn_info->{'Last Changed Rev'}) {
 		croak "Expected r$svn_rev, got r",
@@ -282,12 +286,42 @@ sub assert_svn_wc_clean {
 		print STDERR $_ foreach @status;
 		croak;
 	}
-	my ($tree_a) = grep(/^tree $sha1$/o,`git-cat-file commit $commit`);
-	$tree_a =~ s/^tree //;
-	chomp $tree_a;
-	chomp(my $tree_b = `GIT_INDEX_FILE=$GIT_SVN_INDEX git-write-tree`);
-	if ($tree_a ne $tree_b) {
-		croak "$svn_rev != $commit, $tree_a != $tree_b\n";
+	assert_tree($treeish);
+}
+
+sub assert_tree {
+	my ($treeish) = @_;
+	croak "Not a sha1: $treeish\n" unless $treeish =~ /^$sha1$/o;
+	chomp(my $type = `git-cat-file -t $treeish`);
+	my $expected;
+	while ($type eq 'tag') {
+		chomp(($treeish, $type) = `git-cat-file tag $treeish`);
+	}
+	if ($type eq 'commit') {
+		$expected = (grep /^tree /,`git-cat-file commit $treeish`)[0];
+		($expected) = ($expected =~ /^tree ($sha1)$/);
+		die "Unable to get tree from $treeish\n" unless $expected;
+	} elsif ($type eq 'tree') {
+		$expected = $treeish;
+	} else {
+		die "$treeish is a $type, expected tree, tag or commit\n";
+	}
+
+	my $old_index = $ENV{GIT_INDEX_FILE};
+	my $tmpindex = $GIT_SVN_INDEX.'.assert-tmp';
+	if (-e $tmpindex) {
+		unlink $tmpindex or croak $!;
+	}
+	$ENV{GIT_INDEX_FILE} = $tmpindex;
+	git_addremove();
+	chomp(my $tree = `git-write-tree`);
+	if ($old_index) {
+		$ENV{GIT_INDEX_FILE} = $old_index;
+	} else {
+		delete $ENV{GIT_INDEX_FILE};
+	}
+	if ($tree ne $expected) {
+		croak "Tree mismatch, Got: $tree, Expected: $expected\n";
 	}
 }
 
@@ -298,7 +332,6 @@ sub parse_diff_tree {
 	my @mods;
 	while (<$diff_fh>) {
 		chomp $_; # this gets rid of the trailing "\0"
-		print $_,"\n";
 		if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s
 					$sha1\s($sha1)\s([MTCRAD])\d*$/xo) {
 			push @mods, {	mode_a => $1, mode_b => $2,
@@ -309,36 +342,44 @@ sub parse_diff_tree {
 				$state = 'file_b';
 			}
 		} elsif ($state eq 'file_a') {
-			my $x = $mods[$#mods] or croak __LINE__,": Empty array\n";
+			my $x = $mods[$#mods] or croak "Empty array\n";
 			if ($x->{chg} !~ /^(?:C|R)$/) {
-				croak __LINE__,": Error parsing $_, $x->{chg}\n";
+				croak "Error parsing $_, $x->{chg}\n";
 			}
 			$x->{file_a} = $_;
 			$state = 'file_b';
 		} elsif ($state eq 'file_b') {
-			my $x = $mods[$#mods] or croak __LINE__,": Empty array\n";
+			my $x = $mods[$#mods] or croak "Empty array\n";
 			if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) {
-				croak __LINE__,": Error parsing $_, $x->{chg}\n";
+				croak "Error parsing $_, $x->{chg}\n";
 			}
 			if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) {
-				croak __LINE__,": Error parsing $_, $x->{chg}\n";
+				croak "Error parsing $_, $x->{chg}\n";
 			}
 			$x->{file_b} = $_;
 			$state = 'meta';
 		} else {
-			croak __LINE__,": Error parsing $_\n";
+			croak "Error parsing $_\n";
 		}
 	}
 	close $diff_fh or croak $!;
+
 	return \@mods;
 }
 
 sub svn_check_prop_executable {
 	my $m = shift;
-	if ($m->{mode_b} =~ /755$/ && $m->{mode_a} !~ /755$/) {
-		sys(qw(svn propset svn:executable 1), $m->{file_b});
+	return if -l $m->{file_b};
+	if ($m->{mode_b} =~ /755$/) {
+		chmod((0755 &~ umask),$m->{file_b}) or croak $!;
+		if ($m->{mode_a} !~ /755$/) {
+			sys(qw(svn propset svn:executable 1), $m->{file_b});
+		}
+		-x $m->{file_b} or croak "$m->{file_b} is not executable!\n";
 	} elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) {
 		sys(qw(svn propdel svn:executable), $m->{file_b});
+		chmod((0644 &~ umask),$m->{file_b}) or croak $!;
+		-x $m->{file_b} and croak "$m->{file_b} is executable!\n";
 	}
 }
 
@@ -349,84 +390,166 @@ sub svn_ensure_parent_path {
 	sys(qw(svn add -N), $dir_b) unless (-d "$dir_b/.svn");
 }
 
+sub precommit_check {
+	my $mods = shift;
+	my (%rm_file, %rmdir_check, %added_check);
+
+	my %o = ( D => 0, R => 1, C => 2, A => 3, M => 3, T => 3 );
+	foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
+		if ($m->{chg} eq 'R') {
+			if (-d $m->{file_b}) {
+				err_dir_to_file("$m->{file_a} => $m->{file_b}");
+			}
+			# dir/$file => dir/file/$file
+			my $dirname = dirname($m->{file_b});
+			while ($dirname ne File::Spec->curdir) {
+				if ($dirname ne $m->{file_a}) {
+					$dirname = dirname($dirname);
+					next;
+				}
+				err_file_to_dir("$m->{file_a} => $m->{file_b}");
+			}
+			# baz/zzz => baz (baz is a file)
+			$dirname = dirname($m->{file_a});
+			while ($dirname ne File::Spec->curdir) {
+				if ($dirname ne $m->{file_b}) {
+					$dirname = dirname($dirname);
+					next;
+				}
+				err_dir_to_file("$m->{file_a} => $m->{file_b}");
+			}
+		}
+		if ($m->{chg} =~ /^(D|R)$/) {
+			my $t = $1 eq 'D' ? 'file_b' : 'file_a';
+			$rm_file{ $m->{$t} } = 1;
+			my $dirname = dirname( $m->{$t} );
+			my $basename = basename( $m->{$t} );
+			$rmdir_check{$dirname}->{$basename} = 1;
+		} elsif ($m->{chg} =~ /^(?:A|C)$/) {
+			if (-d $m->{file_b}) {
+				err_dir_to_file($m->{file_b});
+			}
+			my $dirname = dirname( $m->{file_b} );
+			my $basename = basename( $m->{file_b} );
+			$added_check{$dirname}->{$basename} = 1;
+			while ($dirname ne File::Spec->curdir) {
+				if ($rm_file{$dirname}) {
+					err_file_to_dir($m->{file_b});
+				}
+				$dirname = dirname $dirname;
+			}
+		}
+	}
+	return (\%rmdir_check, \%added_check);
+
+	sub err_dir_to_file {
+		my $file = shift;
+		print STDERR "Node change from directory to file ",
+				"is not supported by Subversion: ",$file,"\n";
+		exit 1;
+	}
+	sub err_file_to_dir {
+		my $file = shift;
+		print STDERR "Node change from file to directory ",
+				"is not supported by Subversion: ",$file,"\n";
+		exit 1;
+	}
+}
+
 sub svn_checkout_tree {
-	my ($svn_rev, $commit) = @_;
+	my ($svn_rev, $treeish) = @_;
 	my $from = file_to_s("$REV_DIR/$svn_rev");
 	assert_svn_wc_clean($svn_rev,$from);
-	print "diff-tree '$from' '$commit'\n";
+	print "diff-tree '$from' '$treeish'\n";
 	my $pid = open my $diff_fh, '-|';
 	defined $pid or croak $!;
 	if ($pid == 0) {
 		my @diff_tree = qw(git-diff-tree -z -r -C);
 		push @diff_tree, '--find-copies-harder' if $_find_copies_harder;
 		push @diff_tree, "-l$_l" if defined $_l;
-		exec(@diff_tree, $from, $commit) or croak $!;
+		exec(@diff_tree, $from, $treeish) or croak $!;
 	}
 	my $mods = parse_diff_tree($diff_fh);
 	unless (@$mods) {
 		# git can do empty commits, SVN doesn't allow it...
-		return $svn_rev;
+		return $mods;
 	}
-	my %rm;
-	foreach my $m (@$mods) {
+	my ($rm, $add) = precommit_check($mods);
+
+	my %o = ( D => 1, R => 0, C => -1, A => 3, M => 3, T => 3 );
+	foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
 		if ($m->{chg} eq 'C') {
 			svn_ensure_parent_path( $m->{file_b} );
 			sys(qw(svn cp),		$m->{file_a}, $m->{file_b});
-			blob_to_file(		$m->{sha1_b}, $m->{file_b});
+			apply_mod_line_blob($m);
 			svn_check_prop_executable($m);
 		} elsif ($m->{chg} eq 'D') {
-			$rm{dirname $m->{file_b}}->{basename $m->{file_b}} = 1;
 			sys(qw(svn rm --force), $m->{file_b});
 		} elsif ($m->{chg} eq 'R') {
 			svn_ensure_parent_path( $m->{file_b} );
 			sys(qw(svn mv --force), $m->{file_a}, $m->{file_b});
-			blob_to_file(		$m->{sha1_b}, $m->{file_b});
+			apply_mod_line_blob($m);
 			svn_check_prop_executable($m);
-			$rm{dirname $m->{file_a}}->{basename $m->{file_a}} = 1;
 		} elsif ($m->{chg} eq 'M') {
-			if ($m->{mode_b} =~ /^120/ && $m->{mode_a} =~ /^120/) {
-				unlink $m->{file_b} or croak $!;
-				blob_to_symlink($m->{sha1_b}, $m->{file_b});
-			} else {
-				blob_to_file($m->{sha1_b}, $m->{file_b});
-			}
+			apply_mod_line_blob($m);
 			svn_check_prop_executable($m);
 		} elsif ($m->{chg} eq 'T') {
 			sys(qw(svn rm --force),$m->{file_b});
-			if ($m->{mode_b} =~ /^120/ && $m->{mode_a} =~ /^100/) {
-				blob_to_symlink($m->{sha1_b}, $m->{file_b});
-			} else {
-				blob_to_file($m->{sha1_b}, $m->{file_b});
-			}
-			svn_check_prop_executable($m);
+			apply_mod_line_blob($m);
 			sys(qw(svn add --force), $m->{file_b});
+			svn_check_prop_executable($m);
 		} elsif ($m->{chg} eq 'A') {
 			svn_ensure_parent_path( $m->{file_b} );
-			blob_to_file(		$m->{sha1_b}, $m->{file_b});
-			if ($m->{mode_b} =~ /755$/) {
-				chmod 0755, $m->{file_b};
-			}
+			apply_mod_line_blob($m);
 			sys(qw(svn add --force), $m->{file_b});
+			svn_check_prop_executable($m);
 		} else {
 			croak "Invalid chg: $m->{chg}\n";
 		}
 	}
-	if ($_rmdir) {
-		my $old_index = $ENV{GIT_INDEX_FILE};
-		$ENV{GIT_INDEX_FILE} = $GIT_SVN_INDEX;
-		foreach my $dir (keys %rm) {
-			my $files = $rm{$dir};
-			my @files;
-			foreach (safe_qx('svn','ls',$dir)) {
-				chomp;
-				push @files, $_ unless $files->{$_};
-			}
-			sys(qw(svn rm),$dir) unless @files;
-		}
-		if ($old_index) {
-			$ENV{GIT_INDEX_FILE} = $old_index;
-		} else {
-			delete $ENV{GIT_INDEX_FILE};
+
+	assert_tree($treeish);
+	if ($_rmdir) { # remove empty directories
+		handle_rmdir($rm, $add);
+	}
+	assert_tree($treeish);
+	return $mods;
+}
+
+# svn ls doesn't work with respect to the current working tree, but what's
+# in the repository.  There's not even an option for it... *sigh*
+# (added files don't show up and removed files remain in the ls listing)
+sub svn_ls_current {
+	my ($dir, $rm, $add) = @_;
+	chomp(my @ls = safe_qx('svn','ls',$dir));
+	my @ret = ();
+	foreach (@ls) {
+		s#/$##; # trailing slashes are evil
+		push @ret, $_ unless $rm->{$dir}->{$_};
+	}
+	if (exists $add->{$dir}) {
+		push @ret, keys %{$add->{$dir}};
+	}
+	return \@ret;
+}
+
+sub handle_rmdir {
+	my ($rm, $add) = @_;
+
+	foreach my $dir (sort {length $b <=> length $a} keys %$rm) {
+		my $ls = svn_ls_current($dir, $rm, $add);
+		next if (scalar @$ls);
+		sys(qw(svn rm --force),$dir);
+
+		my $dn = dirname $dir;
+		$rm->{ $dn }->{ basename $dir } = 1;
+		$ls = svn_ls_current($dn, $rm, $add);
+		while (scalar @$ls == 0 && $dn ne File::Spec->curdir) {
+			sys(qw(svn rm --force),$dn);
+			$dir = basename $dn;
+			$dn = dirname $dn;
+			$rm->{ $dn }->{ $dir } = 1;
+			$ls = svn_ls_current($dn, $rm, $add);
 		}
 	}
 }
@@ -692,10 +815,23 @@ sub git_commit {
 	return $commit;
 }
 
+sub apply_mod_line_blob {
+	my $m = shift;
+	if ($m->{mode_b} =~ /^120/) {
+		blob_to_symlink($m->{sha1_b}, $m->{file_b});
+	} else {
+		blob_to_file($m->{sha1_b}, $m->{file_b});
+	}
+}
+
 sub blob_to_symlink {
 	my ($blob, $link) = @_;
 	defined $link or croak "\$link not defined!\n";
 	croak "Not a sha1: $blob\n" unless $blob =~ /^$sha1$/o;
+	if (-l $link || -f _) {
+		unlink $link or croak $!;
+	}
+
 	my $dest = `git-cat-file blob $blob`; # no newline, so no chomp
 	symlink $dest, $link or croak $!;
 }
@@ -704,6 +840,10 @@ sub blob_to_file {
 	my ($blob, $file) = @_;
 	defined $file or croak "\$file not defined!\n";
 	croak "Not a sha1: $blob\n" unless $blob =~ /^$sha1$/o;
+	if (-l $file || -f _) {
+		unlink $file or croak $!;
+	}
+
 	open my $blob_fh, '>', $file or croak "$!: $file\n";
 	my $pid = fork;
 	defined $pid or croak $!;
-- 
1.2.0.gdee6

^ permalink raw reply related

* [PATCH 4/9] git-svn: remove any need for the XML::Simple dependency
From: Eric Wong @ 2006-02-20 18:57 UTC (permalink / raw)
  To: git; +Cc: junkio, Eric Wong
In-Reply-To: <1140461846433-git-send-email-normalperson@yhbt.net>

XML::Simple was originally required back when I made svn-arch-mirror
because I needed to explictly track renames with Arch.  Then I carried
it over to git-svn because I was afraid somebody could commit an svn
log message that could throw off a non-XML log parser.  Then I noticed
the <n> lines column in the header.  So, no more XML :)

Signed-off-by: Eric Wong <normalperson@yhbt.net>

---

 contrib/git-svn/git-svn |   84 ++++++++++++++++++++---------------------------
 1 files changed, 35 insertions(+), 49 deletions(-)

9b380ed2f8f1b18f95d12b86cb760f95e6e0cefe
diff --git a/contrib/git-svn/git-svn b/contrib/git-svn/git-svn
index 5f23d6b..4391bc3 100755
--- a/contrib/git-svn/git-svn
+++ b/contrib/git-svn/git-svn
@@ -21,7 +21,7 @@ $ENV{LC_ALL} = 'C';
 
 # If SVN:: library support is added, please make the dependencies
 # optional and preserve the capability to use the command-line client.
-# See what I do with XML::Simple to make the dependency optional.
+# use eval { require SVN::... } to make it lazy load
 use Carp qw/croak/;
 use IO::File qw//;
 use File::Basename qw/dirname basename/;
@@ -177,8 +177,7 @@ sub fetch {
 	push @log_args, "-r$_revision";
 	push @log_args, '--stop-on-copy' unless $_no_stop_copy;
 
-	eval { require XML::Simple or croak $! };
-	my $svn_log = $@ ? svn_log_raw(@log_args) : svn_log_xml(@log_args);
+	my $svn_log = svn_log_raw(@log_args);
 	@$svn_log = sort { $a->{revision} <=> $b->{revision} } @$svn_log;
 
 	my $base = shift @$svn_log or croak "No base revision!\n";
@@ -476,49 +475,6 @@ sub svn_commit_tree {
 	return fetch("$rev_committed=$commit")->{revision};
 }
 
-sub svn_log_xml {
-	my (@log_args) = @_;
-	my $log_fh = IO::File->new_tmpfile or croak $!;
-
-	my $pid = fork;
-	defined $pid or croak $!;
-
-	if ($pid == 0) {
-		open STDOUT, '>&', $log_fh or croak $!;
-		exec (qw(svn log --xml), @log_args) or croak $!
-	}
-
-	waitpid $pid, 0;
-	croak $? if $?;
-
-	seek $log_fh, 0, 0;
-	my @svn_log;
-	my $log = XML::Simple::XMLin( $log_fh,
-				ForceArray => ['path','revision','logentry'],
-				KeepRoot => 0,
-				KeyAttr => {	logentry => '+revision',
-						paths => '+path' },
-			)->{logentry};
-	foreach my $r (sort {$a <=> $b} keys %$log) {
-		my $log_msg = $log->{$r};
-		my ($Y,$m,$d,$H,$M,$S) = ($log_msg->{date} =~
-					/(\d{4})\-(\d\d)\-(\d\d)T
-					 (\d\d)\:(\d\d)\:(\d\d)\.\d+Z$/x)
-					 or croak "Failed to parse date: ",
-						 $log->{$r}->{date};
-		$log_msg->{date} = "+0000 $Y-$m-$d $H:$M:$S";
-
-		# XML::Simple can't handle <msg></msg> as a string:
-		if (ref $log_msg->{msg} eq 'HASH') {
-			$log_msg->{msg} = "\n";
-		} else {
-			$log_msg->{msg} .= "\n";
-		}
-		push @svn_log, $log->{$r};
-	}
-	return \@svn_log;
-}
-
 sub svn_log_raw {
 	my (@log_args) = @_;
 	my $pid = open my $log_fh,'-|';
@@ -529,21 +485,42 @@ sub svn_log_raw {
 	}
 
 	my @svn_log;
-	my $state;
+	my $state = 'sep';
 	while (<$log_fh>) {
 		chomp;
 		if (/^\-{72}$/) {
+			if ($state eq 'msg') {
+				if ($svn_log[$#svn_log]->{lines}) {
+					$svn_log[$#svn_log]->{msg} .= $_."\n";
+					unless(--$svn_log[$#svn_log]->{lines}) {
+						$state = 'sep';
+					}
+				} else {
+					croak "Log parse error at: $_\n",
+						$svn_log[$#svn_log]->{revision},
+						"\n";
+				}
+				next;
+			}
+			if ($state ne 'sep') {
+				croak "Log parse error at: $_\n",
+					"state: $state\n",
+					$svn_log[$#svn_log]->{revision},
+					"\n";
+			}
 			$state = 'rev';
 
 			# if we have an empty log message, put something there:
 			if (@svn_log) {
 				$svn_log[$#svn_log]->{msg} ||= "\n";
+				delete $svn_log[$#svn_log]->{lines};
 			}
 			next;
 		}
 		if ($state eq 'rev' && s/^r(\d+)\s*\|\s*//) {
 			my $rev = $1;
-			my ($author, $date) = split(/\s*\|\s*/, $_, 2);
+			my ($author, $date, $lines) = split(/\s*\|\s*/, $_, 3);
+			($lines) = ($lines =~ /(\d+)/);
 			my ($Y,$m,$d,$H,$M,$S,$tz) = ($date =~
 					/(\d{4})\-(\d\d)\-(\d\d)\s
 					 (\d\d)\:(\d\d)\:(\d\d)\s([\-\+]\d+)/x)
@@ -551,6 +528,7 @@ sub svn_log_raw {
 			my %log_msg = (	revision => $rev,
 					date => "$tz $Y-$m-$d $H:$M:$S",
 					author => $author,
+					lines => $lines,
 					msg => '' );
 			push @svn_log, \%log_msg;
 			$state = 'msg_start';
@@ -560,7 +538,15 @@ sub svn_log_raw {
 		if ($state eq 'msg_start' && /^$/) {
 			$state = 'msg';
 		} elsif ($state eq 'msg') {
-			$svn_log[$#svn_log]->{msg} .= $_."\n";
+			if ($svn_log[$#svn_log]->{lines}) {
+				$svn_log[$#svn_log]->{msg} .= $_."\n";
+				unless (--$svn_log[$#svn_log]->{lines}) {
+					$state = 'sep';
+				}
+			} else {
+				croak "Log parse error at: $_\n",
+					$svn_log[$#svn_log]->{revision},"\n";
+			}
 		}
 	}
 	close $log_fh or croak $?;
-- 
1.2.0.gdee6

^ permalink raw reply related

* [PATCH 6/9] contrib/git-svn.txt: add a note about renamed/copied directory support
From: Eric Wong @ 2006-02-20 18:57 UTC (permalink / raw)
  To: git; +Cc: junkio, Eric Wong
In-Reply-To: <11404618481876-git-send-email-normalperson@yhbt.net>

Signed-off-by: Eric Wong <normalperson@yhbt.net>

---

 contrib/git-svn/git-svn.txt |    7 +++++++
 1 files changed, 7 insertions(+), 0 deletions(-)

72331d46b99b182406e06070e905123f76abbac8
diff --git a/contrib/git-svn/git-svn.txt b/contrib/git-svn/git-svn.txt
index 07a236f..cf098d7 100644
--- a/contrib/git-svn/git-svn.txt
+++ b/contrib/git-svn/git-svn.txt
@@ -206,6 +206,13 @@ working trees with metadata files.
 svn:keywords can't be ignored in Subversion (at least I don't know of
 a way to ignore them).
 
+Renamed and copied directories are not detected by git and hence not
+tracked when committing to SVN.  I do not plan on adding support for
+this as it's quite difficult and time-consuming to get working for all
+the possible corner cases (git doesn't do it, either).  Renamed and
+copied files are fully supported if they're similar enough for git to
+detect them.
+
 Author
 ------
 Written by Eric Wong <normalperson@yhbt.net>.
-- 
1.2.0.gdee6

^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox