From 81347223066fee64997cbb731925bc8c1abd35d7 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 12 Feb 2007 22:48:56 -0500 Subject: [PATCH 001/201] git-gui: Refactor 'exec git subcmd' idiom. As we frequently need to execute a Git subcommand and obtain its returned output we are making heavy use of [exec git foo] to run foo. As I'm concerned about possibly needing to carry environment data through a shell on Cygwin for at least some subcommands, I'm migrating all current calls to a new git proc. This actually makes the code look cleaner too, as we aren't saying 'exec git' everywhere. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 54 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index f5010dd47a..7ecb98b900 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -46,7 +46,7 @@ proc gitdir {args} { proc gitexec {args} { global _gitexec if {$_gitexec eq {}} { - if {[catch {set _gitexec [exec git --exec-path]} err]} { + if {[catch {set _gitexec [git --exec-path]} err]} { error "Git not installed?\n\n$err" } } @@ -202,14 +202,14 @@ proc save_config {} { set value $global_config_new($name) if {$value ne $global_config($name)} { if {$value eq $default_config($name)} { - catch {exec git config --global --unset $name} + catch {git config --global --unset $name} } else { regsub -all "\[{}\]" $value {"} value - exec git config --global $name $value + git config --global $name $value } set global_config($name) $value if {$value eq $repo_config($name)} { - catch {exec git config --unset $name} + catch {git config --unset $name} set repo_config($name) $value } } @@ -219,16 +219,24 @@ proc save_config {} { set value $repo_config_new($name) if {$value ne $repo_config($name)} { if {$value eq $global_config($name)} { - catch {exec git config --unset $name} + catch {git config --unset $name} } else { regsub -all "\[{}\]" $value {"} value - exec git config $name $value + git config $name $value } set repo_config($name) $value } } } +###################################################################### +## +## handy utils + +proc git {args} { + return [eval exec git $args] +} + proc error_popup {msg} { set title [appname] if {[reponame] ne {}} { @@ -292,7 +300,7 @@ proc ask_popup {msg} { ## repository setup if { [catch {set _gitdir $env(GIT_DIR)}] - && [catch {set _gitdir [exec git rev-parse --git-dir]} err]} { + && [catch {set _gitdir [git rev-parse --git-dir]} err]} { catch {wm withdraw .} error_popup "Cannot find the git directory:\n\n$err" exit 1 @@ -365,7 +373,7 @@ proc repository_state {ctvar hdvar mhvar} { set mh [list] - if {[catch {set current_branch [exec git symbolic-ref HEAD]}]} { + if {[catch {set current_branch [git symbolic-ref HEAD]}]} { set current_branch {} } else { regsub ^refs/((heads|tags|remotes)/)? \ @@ -374,7 +382,7 @@ proc repository_state {ctvar hdvar mhvar} { current_branch } - if {[catch {set hd [exec git rev-parse --verify HEAD]}]} { + if {[catch {set hd [git rev-parse --verify HEAD]}]} { set hd {} set ct initial return @@ -402,7 +410,7 @@ proc PARENT {} { return $p } if {$empty_tree eq {}} { - set empty_tree [exec git mktree << {}] + set empty_tree [git mktree << {}] } return $empty_tree } @@ -1042,7 +1050,7 @@ proc committer_ident {} { global GIT_COMMITTER_IDENT if {$GIT_COMMITTER_IDENT eq {}} { - if {[catch {set me [exec git var GIT_COMMITTER_IDENT]} err]} { + if {[catch {set me [git var GIT_COMMITTER_IDENT]} err]} { error_popup "Unable to obtain your identity:\n\n$err" return {} } @@ -1275,7 +1283,7 @@ proc commit_committree {fd_wt curHEAD msg} { # -- Let rerere do its thing. # if {[file isdirectory [gitdir rr-cache]]} { - catch {exec git rerere} + catch {git rerere} } # -- Run the post-commit hook. @@ -1894,7 +1902,7 @@ proc do_create_branch_action {w} { focus $w.desc.name_t return } - if {![catch {exec git show-ref --verify -- "refs/heads/$newbranch"}]} { + if {![catch {git show-ref --verify -- "refs/heads/$newbranch"}]} { tk_messageBox \ -icon error \ -type ok \ @@ -1904,7 +1912,7 @@ proc do_create_branch_action {w} { focus $w.desc.name_t return } - if {[catch {exec git check-ref-format "heads/$newbranch"}]} { + if {[catch {git check-ref-format "heads/$newbranch"}]} { tk_messageBox \ -icon error \ -type ok \ @@ -1921,7 +1929,7 @@ proc do_create_branch_action {w} { tracking {set rev $create_branch_trackinghead} expression {set rev $create_branch_revexp} } - if {[catch {set cmt [exec git rev-parse --verify "${rev}^0"]}]} { + if {[catch {set cmt [git rev-parse --verify "${rev}^0"]}]} { tk_messageBox \ -icon error \ -type ok \ @@ -2100,7 +2108,7 @@ proc do_delete_branch_action {w} { } if {$check_rev eq {:none}} { set check_cmt {} - } elseif {[catch {set check_cmt [exec git rev-parse --verify "${check_rev}^0"]}]} { + } elseif {[catch {set check_cmt [git rev-parse --verify "${check_rev}^0"]}]} { tk_messageBox \ -icon error \ -type ok \ @@ -2114,10 +2122,10 @@ proc do_delete_branch_action {w} { set not_merged [list] foreach i [$w.list.l curselection] { set b [$w.list.l get $i] - if {[catch {set o [exec git rev-parse --verify $b]}]} continue + if {[catch {set o [git rev-parse --verify $b]}]} continue if {$check_cmt ne {}} { if {$b eq $check_rev} continue - if {[catch {set m [exec git merge-base $o $check_cmt]}]} continue + if {[catch {set m [git merge-base $o $check_cmt]}]} continue if {$o ne $m} { lappend not_merged $b continue @@ -2155,7 +2163,7 @@ Delete the selected branches?} foreach i $to_delete { set b [lindex $i 0] set o [lindex $i 1] - if {[catch {exec git update-ref -d "refs/heads/$b" $o} err]} { + if {[catch {git update-ref -d "refs/heads/$b" $o} err]} { append failed " - $b: $err\n" } else { set x [lsearch -sorted -exact $all_heads $b] @@ -2366,7 +2374,7 @@ Staying on branch '$current_branch'." # here, it Just Works(tm). If it doesn't we are in some really ugly # state that is difficult to recover from within git-gui. # - if {[catch {exec git symbolic-ref HEAD "refs/heads/$new_branch"} err]} { + if {[catch {git symbolic-ref HEAD "refs/heads/$new_branch"} err]} { error_popup "Failed to set current branch. This working directory is only partially switched. @@ -4161,7 +4169,7 @@ proc do_quit {} { set rc_geometry {} } if {$cfg_geometry ne $rc_geometry} { - catch {exec git config gui.geometry $cfg_geometry} + catch {git config gui.geometry $cfg_geometry} } } @@ -4412,7 +4420,7 @@ $copyright" \ set v {} append v "[appname] version $appvers\n" - append v "[exec git version]\n" + append v "[git version]\n" append v "\n" if {$tcl_patchLevel eq $tk_patchLevel} { append v "Tcl/Tk version $tcl_patchLevel" @@ -5904,7 +5912,7 @@ if {[is_enabled transport]} { if {[is_enabled multicommit]} { set object_limit 2000 if {[is_Windows]} {set object_limit 200} - regexp {^([0-9]+) objects,} [exec git count-objects] _junk objects_current + regexp {^([0-9]+) objects,} [git count-objects] _junk objects_current if {$objects_current >= $object_limit} { if {[ask_popup \ "This repository currently has $objects_current loose objects. From 54acdd95b81d3675381a749e7cd7fc14956853c3 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 13 Feb 2007 23:15:25 -0500 Subject: [PATCH 002/201] git-gui: Basic version check to ensure git 1.5.0 or later is used. This is a very crude (but hopefully effective) check against the `git` executable found in our PATH. Some of the subcommands and options that git-gui requires to be present to operate were created during the 1.5.0 development cycle, so 1.5 is the minimum version of git that we can expect to support. There actually are early releases of 1.5 (e.g. 1.5.0-rc0) that don't have everything we expect (like `blame --incremental`) but these are purely academic at this point. 1.5.0 final was tagged and released just a few hours ago. The release candidates will (hopefully) fade into the dark quickly. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/git-gui.sh b/git-gui.sh index 7ecb98b900..444cc0afc8 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -295,6 +295,38 @@ proc ask_popup {msg} { -message $msg] } +###################################################################### +## +## version check + +set req_maj 1 +set req_min 5 + +if {[catch {set v [git --version]} err]} { + catch {wm withdraw .} + error_popup "Cannot determine Git version: + +$err + +[appname] requires Git $req_maj.$req_min or later." + exit 1 +} +if {[regexp {^git version (\d+)\.(\d+)} $v _junk act_maj act_min]} { + if {$act_maj < $req_maj + || ($act_maj == $req_maj && $act_min < $req_min)} { + catch {wm withdraw .} + error_popup "[appname] requires Git $req_maj.$req_min or later. + +You are using $v." + exit 1 + } +} else { + catch {wm withdraw .} + error_popup "Cannot parse Git version string:\n\n$v" + exit 1 +} +unset -nocomplain v _junk act_maj act_min req_maj req_min + ###################################################################### ## ## repository setup From cdf6e08880b4924fa717bd6ca700081f445c9065 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 13 Feb 2007 23:43:48 -0500 Subject: [PATCH 003/201] git-gui: Permit merging tags into the current branch. It was pointed out on the git mailing list by Martin Koegler that we did not show tags as possible things to merge into the current branch. They actually are, and core Git's Grand Unified Merge Driver will accept them just like any other commit. So our merge dialog now requests all refs/heads, refs/remotes and refs/tags named refs and attempts to match them against the commits not in HEAD. One complicating factor here is that we must use the %(*objectname) field when talking about an annotated tag, as they will not appear in the output of rev-list. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 444cc0afc8..f8d4db21a5 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2916,14 +2916,16 @@ proc do_local_merge {} { pack $w.source -fill both -expand 1 -pady 5 -padx 5 set cmd [list git for-each-ref] - lappend cmd {--format=%(objectname) %(refname)} + lappend cmd {--format=%(objectname) %(*objectname) %(refname)} lappend cmd refs/heads lappend cmd refs/remotes + lappend cmd refs/tags set fr_fd [open "| $cmd" r] fconfigure $fr_fd -translation binary while {[gets $fr_fd line] > 0} { set line [split $line { }] - set sha1([lindex $line 0]) [lindex $line 1] + set sha1([lindex $line 0]) [lindex $line 2] + set sha1([lindex $line 1]) [lindex $line 2] } close $fr_fd @@ -2931,7 +2933,7 @@ proc do_local_merge {} { set fr_fd [open "| git rev-list --all --not HEAD"] while {[gets $fr_fd line] > 0} { if {[catch {set ref $sha1($line)}]} continue - regsub ^refs/(heads|remotes)/ $ref {} ref + regsub ^refs/(heads|remotes|tags)/ $ref {} ref lappend to_show $ref } close $fr_fd From 5ac58f5ba1392416a911c618abba6d874987a1b0 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 14 Feb 2007 00:10:20 -0500 Subject: [PATCH 004/201] git-gui: More consistently display the application name. I started to find it confusing that git-gui would refer to itself as git-citool when it was started through the citool hardlink, or with the citool subcommand. What was especially confusing was the options dialog and the about dialog, as both seemed to imply they were somehow different from the git-gui versions. In actuality there is no difference at all. Now we just call our options menu item 'Options...' (skipping the application name) and our About dialog now always shows git-gui within the short description (above the copyleft notice) and in the version field. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index f8d4db21a5..b3a80c64ef 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -4442,7 +4442,7 @@ proc do_about {} { pack $w.buttons -side bottom -fill x -pady 10 -padx 10 label $w.desc \ - -text "[appname] - a commit creation tool for Git. + -text "git-gui - a commit creation tool for Git. $copyright" \ -padx 5 -pady 5 \ -justify left \ @@ -4453,7 +4453,7 @@ $copyright" \ pack $w.desc -side top -fill x -padx 5 -pady 5 set v {} - append v "[appname] version $appvers\n" + append v "git-gui version $appvers\n" append v "[git version]\n" append v "\n" if {$tcl_patchLevel eq $tk_patchLevel} { @@ -4513,7 +4513,7 @@ proc do_options {} { toplevel $w wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - label $w.header -text "[appname] Options" \ + label $w.header -text "Options" \ -font font_uibold pack $w.header -side top -fill x @@ -5219,7 +5219,7 @@ if {[is_MacOSX]} { .mbar.apple add command -label "About [appname]" \ -command do_about \ -font font_ui - .mbar.apple add command -label "[appname] Options..." \ + .mbar.apple add command -label "Options..." \ -command do_options \ -font font_ui } else { From ed3adde081645c5f93685b22d39f2d74e068873d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 14 Feb 2007 00:28:00 -0500 Subject: [PATCH 005/201] git-gui: Print version on the console. Like `git version`, `git gui version` (or `git gui --version`) shows the version of git-gui, in case the user needs to know this, without looking at it in the GUI about dialog. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/git-gui.sh b/git-gui.sh index b3a80c64ef..04afb36343 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -4987,6 +4987,8 @@ enable_option branch enable_option transport switch -- $subcommand { +--version - +version - blame { disable_option multicommit disable_option branch @@ -5322,6 +5324,11 @@ bind all <$M1B-Key-W> {destroy [winfo toplevel %W]} # -- Not a normal commit type invocation? Do that instead! # switch -- $subcommand { +--version - +version { + puts "git-gui version $appvers" + exit +} blame { if {[llength $argv] != 2} { puts stderr "usage: $argv0 blame commit path" From 26370f73c0e82bd67103e0868f84a44b536ff3fc Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 14 Feb 2007 01:55:16 -0500 Subject: [PATCH 006/201] git-gui: Prefer version file over git-describe. Some distributions are using Git for part of their package management system, but unpack Git's own source code for delivery from the .tar.gz. This means that when we walk up the directory tree with git-describe to locate a Git repository, the repository we find is for the distribution and *not* for git-gui. Consequently any tag we might find there is bogus and does not apply to us. In this case the version file should always exist and be readable, as the packager is working from the released .tar.gz sources. So we should always favor the version file over anything git-describe guess for us. Signed-off-by: Shawn O. Pearce --- GIT-VERSION-GEN | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 9966126da2..2741c1e14c 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -20,6 +20,11 @@ tree_search () done } +# Always use the tarball version file if found, just +# in case we are somehow contained in a larger git +# repository that doesn't actually track our state. +# (At least one package manager is doing this.) +# # We may be a subproject, so try looking for the merge # commit that supplied this directory content if we are # not at the toplevel. We probably will always be the @@ -27,10 +32,13 @@ tree_search () # that fact. # # If we are at the toplevel or the merge assumption fails -# try looking for a gitgui-* tag, or fallback onto the -# distributed version file. +# try looking for a gitgui-* tag. -if prefix="$(git rev-parse --show-prefix 2>/dev/null)" +if test -f version && + VN=$(cat version) +then + : happy +elif prefix="$(git rev-parse --show-prefix 2>/dev/null)" test -n "$prefix" && head=$(git rev-list --max-count=1 HEAD -- . 2>/dev/null) && tree=$(git rev-parse --verify "HEAD:$prefix" 2>/dev/null) && @@ -48,9 +56,6 @@ elif VN=$(git describe --abbrev=4 HEAD 2>/dev/null) && esac then VN=$(echo "$VN" | sed -e 's/^gitgui-//;s/-/./g'); -elif test -f version -then - VN=$(cat version) || VN="$DEF_VER" else VN="$DEF_VER" fi From b2741f63d43a17ce9dafd1f97614bd6dbba72887 Mon Sep 17 00:00:00 2001 From: Andy Parkins Date: Tue, 13 Feb 2007 15:12:45 +0000 Subject: [PATCH 007/201] Have git-cvsserver call hooks/update before really altering the ref git-cvsserver is analogous to git-receive-pack; a checking from a cvs client to a central server is like a git-push from a working repository. Therefore it's nice to use the same access control (and email sending) that a receive-pack would perform. This patch tests for an executable update hook; if it is it is run with the ref being updated and the old and new hashes as normal. If the update hook returns an error code the update is aborted and the ref is never updated. The cvsserver returns "error 1" to the client to signal there was an EPERM error. Signed-off-by: Andy Parkins Signed-off-by: Junio C Hamano --- git-cvsserver.perl | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 9371788fab..84520e7ad5 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -1171,6 +1171,21 @@ sub req_ci exit; } + # Check that this is allowed, just as we would with a receive-pack + my @cmd = ( $ENV{GIT_DIR}.'hooks/update', "refs/heads/$state->{module}", + $parenthash, $commithash ); + if( -x $cmd[0] ) { + unless( system( @cmd ) == 0 ) + { + $log->warn("Commit failed (update hook declined to update ref)"); + print "error 1 Commit failed (update hook declined)\n"; + close LOCKFILE; + unlink($lockfile); + chdir "/"; + exit; + } + } + print LOCKFILE $commithash; $updater->update(); From 6c510bee2013022fbce52f4b0ec0cc593fc0cc48 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 13 Feb 2007 11:07:23 -0800 Subject: [PATCH 008/201] Lazy man's auto-CRLF It currently does NOT know about file attributes, so it does its conversion purely based on content. Maybe that is more in the "git philosophy" anyway, since content is king, but I think we should try to do the file attributes to turn it off on demand. Anyway, BY DEFAULT it is off regardless, because it requires a [core] AutoCRLF = true in your config file to be enabled. We could make that the default for Windows, of course, the same way we do some other things (filemode etc). But you can actually enable it on UNIX, and it will cause: - "git update-index" will write blobs without CRLF - "git diff" will diff working tree files without CRLF - "git checkout" will write files to the working tree _with_ CRLF and things work fine. Funnily, it actually shows an odd file in git itself: git clone -n git test-crlf cd test-crlf git config core.autocrlf true git checkout git diff shows a diff for "Documentation/docbook-xsl.css". Why? Because we have actually checked in that file *with* CRLF! So when "core.autocrlf" is true, we'll always generate a *different* hash for it in the index, because the index hash will be for the content _without_ CRLF. Is this complete? I dunno. It seems to work for me. It doesn't use the filename at all right now, and that's probably a deficiency (we could certainly make the "is_binary()" heuristics also take standard filename heuristics into account). I don't pass in the filename at all for the "index_fd()" case (git-update-index), so that would need to be passed around, but this actually works fine. NOTE NOTE NOTE! The "is_binary()" heuristics are totally made-up by yours truly. I will not guarantee that they work at all reasonable. Caveat emptor. But it _is_ simple, and it _is_ safe, since it's all off by default. The patch is pretty simple - the biggest part is the new "convert.c" file, but even that is really just basic stuff that anybody can write in "Teaching C 101" as a final project for their first class in programming. Not to say that it's bug-free, of course - but at least we're not talking about rocket surgery here. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- Makefile | 3 +- cache.h | 5 ++ config.c | 5 ++ convert.c | 186 ++++++++++++++++++++++++++++++++++++++++++++++++++ diff.c | 17 ++++- entry.c | 16 ++++- environment.c | 1 + sha1_file.c | 23 ++++++- 8 files changed, 251 insertions(+), 5 deletions(-) create mode 100644 convert.c diff --git a/Makefile b/Makefile index 40bdcff696..60496ff957 100644 --- a/Makefile +++ b/Makefile @@ -262,7 +262,8 @@ LIB_OBJS = \ revision.o pager.o tree-walk.o xdiff-interface.o \ write_or_die.o trace.o list-objects.o grep.o \ alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \ - color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o + color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \ + convert.o BUILTIN_OBJS = \ builtin-add.o \ diff --git a/cache.h b/cache.h index c62b0b090d..9c019e8bba 100644 --- a/cache.h +++ b/cache.h @@ -201,6 +201,7 @@ extern const char *apply_default_whitespace; extern int zlib_compression_level; extern size_t packed_git_window_size; extern size_t packed_git_limit; +extern int auto_crlf; #define GIT_REPO_VERSION 0 extern int repository_format_version; @@ -468,4 +469,8 @@ extern int nfvasprintf(char **str, const char *fmt, va_list va); extern void trace_printf(const char *format, ...); extern void trace_argv_printf(const char **argv, int count, const char *format, ...); +/* convert.c */ +extern int convert_to_git(const char *path, char **bufp, unsigned long *sizep); +extern int convert_to_working_tree(const char *path, char **bufp, unsigned long *sizep); + #endif /* CACHE_H */ diff --git a/config.c b/config.c index d82107124a..ffe02129a5 100644 --- a/config.c +++ b/config.c @@ -324,6 +324,11 @@ int git_default_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.autocrlf")) { + auto_crlf = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "user.name")) { strlcpy(git_default_name, value, sizeof(git_default_name)); return 0; diff --git a/convert.c b/convert.c new file mode 100644 index 0000000000..13beb70582 --- /dev/null +++ b/convert.c @@ -0,0 +1,186 @@ +#include "cache.h" +/* + * convert.c - convert a file when checking it out and checking it in. + * + * This should use the pathname to decide on whether it wants to do some + * more interesting conversions (automatic gzip/unzip, general format + * conversions etc etc), but by default it just does automatic CRLF<->LF + * translation when the "auto_crlf" option is set. + */ + +struct text_stat { + /* CR, LF and CRLF counts */ + unsigned cr, lf, crlf; + + /* These are just approximations! */ + unsigned printable, nonprintable; +}; + +static void gather_stats(const char *buf, unsigned long size, struct text_stat *stats) +{ + unsigned long i; + + memset(stats, 0, sizeof(*stats)); + + for (i = 0; i < size; i++) { + unsigned char c = buf[i]; + if (c == '\r') { + stats->cr++; + if (i+1 < size && buf[i+1] == '\n') + stats->crlf++; + continue; + } + if (c == '\n') { + stats->lf++; + continue; + } + if (c == 127) + /* DEL */ + stats->nonprintable++; + else if (c < 32) { + switch (c) { + /* BS, HT, ESC and FF */ + case '\b': case '\t': case '\033': case '\014': + stats->printable++; + break; + default: + stats->nonprintable++; + } + } + else + stats->printable++; + } +} + +/* + * The same heuristics as diff.c::mmfile_is_binary() + */ +static int is_binary(unsigned long size, struct text_stat *stats) +{ + + if ((stats->printable >> 7) < stats->nonprintable) + return 1; + /* + * Other heuristics? Average line length might be relevant, + * as might LF vs CR vs CRLF counts.. + * + * NOTE! It might be normal to have a low ratio of CRLF to LF + * (somebody starts with a LF-only file and edits it with an editor + * that adds CRLF only to lines that are added..). But do we + * want to support CR-only? Probably not. + */ + return 0; +} + +int convert_to_git(const char *path, char **bufp, unsigned long *sizep) +{ + char *buffer, *nbuf; + unsigned long size, nsize; + struct text_stat stats; + + /* + * FIXME! Other pluggable conversions should go here, + * based on filename patterns. Right now we just do the + * stupid auto-CRLF one. + */ + if (!auto_crlf) + return 0; + + size = *sizep; + if (!size) + return 0; + buffer = *bufp; + + gather_stats(buffer, size, &stats); + + /* No CR? Nothing to convert, regardless. */ + if (!stats.cr) + return 0; + + /* + * We're currently not going to even try to convert stuff + * that has bare CR characters. Does anybody do that crazy + * stuff? + */ + if (stats.cr != stats.crlf) + return 0; + + /* + * And add some heuristics for binary vs text, of course... + */ + if (is_binary(size, &stats)) + return 0; + + /* + * Ok, allocate a new buffer, fill it in, and return true + * to let the caller know that we switched buffers on it. + */ + nsize = size - stats.crlf; + nbuf = xmalloc(nsize); + *bufp = nbuf; + *sizep = nsize; + do { + unsigned char c = *buffer++; + if (c != '\r') + *nbuf++ = c; + } while (--size); + + return 1; +} + +int convert_to_working_tree(const char *path, char **bufp, unsigned long *sizep) +{ + char *buffer, *nbuf; + unsigned long size, nsize; + struct text_stat stats; + unsigned char last; + + /* + * FIXME! Other pluggable conversions should go here, + * based on filename patterns. Right now we just do the + * stupid auto-CRLF one. + */ + if (!auto_crlf) + return 0; + + size = *sizep; + if (!size) + return 0; + buffer = *bufp; + + gather_stats(buffer, size, &stats); + + /* No LF? Nothing to convert, regardless. */ + if (!stats.lf) + return 0; + + /* Was it already in CRLF format? */ + if (stats.lf == stats.crlf) + return 0; + + /* If we have any bare CR characters, we're not going to touch it */ + if (stats.cr != stats.crlf) + return 0; + + if (is_binary(size, &stats)) + return 0; + + /* + * Ok, allocate a new buffer, fill it in, and return true + * to let the caller know that we switched buffers on it. + */ + nsize = size + stats.lf - stats.crlf; + nbuf = xmalloc(nsize); + *bufp = nbuf; + *sizep = nsize; + last = 0; + do { + unsigned char c = *buffer++; + if (c == '\n' && last != '\r') + *nbuf++ = '\r'; + *nbuf++ = c; + last = c; + } while (--size); + + return 1; +} diff --git a/diff.c b/diff.c index 13b9b6c560..561587cace 100644 --- a/diff.c +++ b/diff.c @@ -1332,6 +1332,9 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only) reuse_worktree_file(s->path, s->sha1, 0)) { struct stat st; int fd; + char *buf; + unsigned long size; + if (lstat(s->path, &st) < 0) { if (errno == ENOENT) { err_empty: @@ -1364,7 +1367,19 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only) s->data = xmmap(NULL, s->size, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); s->should_munmap = 1; - /* FIXME! CRLF -> LF conversion goes here, based on "s->path" */ + + /* + * Convert from working tree format to canonical git format + */ + buf = s->data; + size = s->size; + if (convert_to_git(s->path, &buf, &size)) { + munmap(s->data, s->size); + s->should_munmap = 0; + s->data = buf; + s->size = size; + s->should_free = 1; + } } else { char type[20]; diff --git a/entry.c b/entry.c index c2641ddefd..472a9ef321 100644 --- a/entry.c +++ b/entry.c @@ -78,6 +78,9 @@ static int write_entry(struct cache_entry *ce, char *path, struct checkout *stat path, sha1_to_hex(ce->sha1)); } switch (ntohl(ce->ce_mode) & S_IFMT) { + char *buf; + unsigned long nsize; + case S_IFREG: if (to_tempfile) { strcpy(path, ".merge_file_XXXXXX"); @@ -89,7 +92,18 @@ static int write_entry(struct cache_entry *ce, char *path, struct checkout *stat return error("git-checkout-index: unable to create file %s (%s)", path, strerror(errno)); } - /* FIXME: LF -> CRLF conversion goes here, based on "ce->name" */ + + /* + * Convert from git internal format to working tree format + */ + buf = new; + nsize = size; + if (convert_to_working_tree(ce->name, &buf, &nsize)) { + free(new); + new = buf; + size = nsize; + } + wrote = write_in_full(fd, new, size); close(fd); free(new); diff --git a/environment.c b/environment.c index 54c22f8248..2fa0960412 100644 --- a/environment.c +++ b/environment.c @@ -28,6 +28,7 @@ size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE; size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT; int pager_in_use; int pager_use_color = 1; +int auto_crlf = 0; static const char *git_dir; static char *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file; diff --git a/sha1_file.c b/sha1_file.c index 8ad7fad825..6ec67b2923 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -2082,7 +2082,7 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, con { unsigned long size = st->st_size; void *buf; - int ret; + int ret, re_allocated = 0; buf = ""; if (size) @@ -2091,11 +2091,30 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, con if (!type) type = blob_type; - /* FIXME: CRLF -> LF conversion here for blobs! We'll need the path! */ + + /* + * Convert blobs to git internal format + */ + if (!strcmp(type, blob_type)) { + unsigned long nsize = size; + char *nbuf = buf; + if (convert_to_git(NULL, &nbuf, &nsize)) { + if (size) + munmap(buf, size); + size = nsize; + buf = nbuf; + re_allocated = 1; + } + } + if (write_object) ret = write_sha1_file(buf, size, type, sha1); else ret = hash_sha1_file(buf, size, type, sha1); + if (re_allocated) { + free(buf); + return ret; + } if (size) munmap(buf, size); return ret; From d7f4633405acf3dc09798a759463c616c7c49dfd Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 13 Feb 2007 18:16:12 -0800 Subject: [PATCH 009/201] Make AutoCRLF ternary variable. This allows you to do: [core] AutoCRLF = input and it should do only the CRLF->LF translation (ie it simplifies CRLF only when reading working tree files, but when checking out files, it leaves the LF alone, and doesn't turn it into a CRLF). Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- config.c | 4 ++++ convert.c | 2 +- environment.c | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/config.c b/config.c index ffe02129a5..e8ae919b52 100644 --- a/config.c +++ b/config.c @@ -325,6 +325,10 @@ int git_default_config(const char *var, const char *value) } if (!strcmp(var, "core.autocrlf")) { + if (value && !strcasecmp(value, "input")) { + auto_crlf = -1; + return 0; + } auto_crlf = git_config_bool(var, value); return 0; } diff --git a/convert.c b/convert.c index 13beb70582..898bfe3eb2 100644 --- a/convert.c +++ b/convert.c @@ -140,7 +140,7 @@ int convert_to_working_tree(const char *path, char **bufp, unsigned long *sizep) * based on filename patterns. Right now we just do the * stupid auto-CRLF one. */ - if (!auto_crlf) + if (auto_crlf <= 0) return 0; size = *sizep; diff --git a/environment.c b/environment.c index 2fa0960412..570e32ac3c 100644 --- a/environment.c +++ b/environment.c @@ -28,7 +28,7 @@ size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE; size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT; int pager_in_use; int pager_use_color = 1; -int auto_crlf = 0; +int auto_crlf = 0; /* 1: both ways, -1: only when adding git objects */ static const char *git_dir; static char *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file; From 634ede32ae7d4c76e96e88f9cd5c1b3a70ea08ac Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 14 Feb 2007 14:54:00 -0800 Subject: [PATCH 010/201] t0020: add test for auto-crlf This tests lowlevel of update/checkout codepaths and some patch application. Currently, variants of "git apply" that look at the working tree files does not work, so it does not test the patch application without parameter and with --index parameter when autocrlf is set to produce CRLF files. We should add test for diff generation too. Signed-off-by: Junio C Hamano --- t/t0020-crlf.sh | 206 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100755 t/t0020-crlf.sh diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh new file mode 100755 index 0000000000..58a4d86df3 --- /dev/null +++ b/t/t0020-crlf.sh @@ -0,0 +1,206 @@ +#!/bin/sh + +test_description='CRLF conversion' + +. ./test-lib.sh + +append_cr () { + sed -e 's/$/Q/' | tr Q '\015' +} + +remove_cr () { + tr '\015' Q <"$1" | grep Q >/dev/null && + tr '\015' Q <"$1" | sed -ne 's/Q$//p' +} + +test_expect_success setup ' + + git repo-config core.autocrlf false && + + for w in Hello world how are you; do echo $w; done >one && + mkdir dir && + for w in I am very very fine thank you; do echo $w; done >dir/two && + git add . && + + git commit -m initial && + + one=`git rev-parse HEAD:one` && + dir=`git rev-parse HEAD:dir` && + two=`git rev-parse HEAD:dir/two` && + + for w in Some extra lines here; do echo $w; done >>one && + git diff >patch.file && + patched=`git hash-object --stdin tmp && mv -f tmp $f && + git update-index -- $f || { + echo Oops + false + break + } + done && + + differs=`git diff-index --cached HEAD` && + test -z "$differs" || { + echo Oops "$differs" + false + } + +' + +test_expect_success 'update with autocrlf=true' ' + + rm -f tmp one dir/two && + git read-tree --reset -u HEAD && + git repo-config core.autocrlf true && + + for f in one dir/two + do + append_cr <$f >tmp && mv -f tmp $f && + git update-index -- $f || { + echo "Oops $f" + false + break + } + done && + + differs=`git diff-index --cached HEAD` && + test -z "$differs" || { + echo Oops "$differs" + false + } + +' + +test_expect_success 'checkout with autocrlf=true' ' + + rm -f tmp one dir/two && + git repo-config core.autocrlf true && + git read-tree --reset -u HEAD && + + for f in one dir/two + do + remove_cr "$f" >tmp && mv -f tmp $f && + git update-index -- $f || { + echo "Eh? $f" + false + break + } + done && + test "$one" = `git hash-object --stdin /dev/null + then + echo "Eh? $f" + false + break + else + git update-index -- $f + fi + done && + test "$one" = `git hash-object --stdin tmp && mv -f tmp one && + + git apply patch.file && + test "$patched" = "`git hash-object --stdin Date: Thu, 15 Feb 2007 01:28:34 -0500 Subject: [PATCH 011/201] git-gui: Create new branches from a tag. I'm missing the possibility to base a new branch on a tag. The following adds a tag drop down to the new branch dialog. Signed-off-by: Martin Koegler Signed-off-by: Shawn O. Pearce --- git-gui.sh | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/git-gui.sh b/git-gui.sh index 04afb36343..9ce5a3bdc3 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1916,11 +1916,24 @@ proc all_tracking_branches {} { return [lsort -unique $all_trackings] } +proc load_all_tags {} { + set all_tags [list] + set fd [open "| git for-each-ref --format=%(refname) refs/tags" r] + while {[gets $fd line] > 0} { + if {![regsub ^refs/tags/ $line {} name]} continue + lappend all_tags $name + } + close $fd + + return [lsort $all_tags] +} + proc do_create_branch_action {w} { global all_heads null_sha1 repo_config global create_branch_checkout create_branch_revtype global create_branch_head create_branch_trackinghead global create_branch_name create_branch_revexp + global create_branch_tag set newbranch $create_branch_name if {$newbranch eq {} @@ -1959,6 +1972,7 @@ proc do_create_branch_action {w} { switch -- $create_branch_revtype { head {set rev $create_branch_head} tracking {set rev $create_branch_trackinghead} + tag {set rev $create_branch_tag} expression {set rev $create_branch_revexp} } if {[catch {set cmt [git rev-parse --verify "${rev}^0"]}]} { @@ -2004,6 +2018,8 @@ trace add variable create_branch_head write \ [list radio_selector create_branch_revtype head] trace add variable create_branch_trackinghead write \ [list radio_selector create_branch_revtype tracking] +trace add variable create_branch_tag write \ + [list radio_selector create_branch_revtype tag] trace add variable delete_branch_head write \ [list radio_selector delete_branch_checktype head] @@ -2015,6 +2031,7 @@ proc do_create_branch {} { global create_branch_checkout create_branch_revtype global create_branch_head create_branch_trackinghead global create_branch_name create_branch_revexp + global create_branch_tag set w .branch_editor toplevel $w @@ -2078,6 +2095,19 @@ proc do_create_branch {} { $all_trackings grid $w.from.tracking_r $w.from.tracking_m -sticky w } + set all_tags [load_all_tags] + if {$all_tags ne {}} { + set create_branch_tag [lindex $all_tags 0] + radiobutton $w.from.tag_r \ + -text {Tag:} \ + -value tag \ + -variable create_branch_revtype \ + -font font_ui + eval tk_optionMenu $w.from.tag_m \ + create_branch_tag \ + $all_tags + grid $w.from.tag_r $w.from.tag_m -sticky w + } radiobutton $w.from.exp_r \ -text {Revision Expression:} \ -value expression \ From 24424fc2f7cd7b3875fe6863587f47c043ac449d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 15 Feb 2007 01:46:27 -0800 Subject: [PATCH 012/201] remotes.not-origin.tagopt With a configuration entry like this: [remote "alt-git"] url = git://repo.or.cz/alt.git/git/ fetch = +refs/heads/*:refs/remotes/alt-git/* tagopt = --no-tags you do not have to say "git pull --no-tags alt-git". Just saying "git pull alt-git" would suffice. Obviously, if you want to get the tag from such an alternate remote in a separate namespace, you could also do something like: [remote "alt-git"] url = git://repo.or.cz/alt.git/git/ fetch = +refs/heads/*:refs/remotes/alt-git/* fetch = +refs/tags/*:refs/remote-tags/alt-git/* tagopt = --no-tags Signed-off-by: Junio C Hamano --- git-fetch.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/git-fetch.sh b/git-fetch.sh index ca984e739a..d230995f6e 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -243,6 +243,15 @@ then orig_head=$(git-rev-parse --verify HEAD 2>/dev/null) fi +# Allow --notags from remote.$1.tagopt +case "$tags$no_tags" in +'') + case "$(git-config --get "remote.$1.tagopt")" in + --no-tags) + no_tags=t ;; + esac +esac + # If --tags (and later --heads or --all) is specified, then we are # not talking about defaults stored in Pull: line of remotes or # branches file, and just fetch those and refspecs explicitly given. From b90d479255d3b47e3604493b58e271cb9cd8dccd Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 16 Feb 2007 00:24:03 -0500 Subject: [PATCH 013/201] git-gui: Expose the browser as a subcommand. Some users may find being able to browse around an arbitrary branch to be handy, so we now expose our graphical browser through `git gui browse `. Yes, I'm being somewhat lazy and making the user give us the name of the branch to browse. They can always enter HEAD. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 9ce5a3bdc3..e7898014ac 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -3044,7 +3044,14 @@ proc new_browser {commit} { global next_browser_id cursor_ptr M1B global browser_commit browser_status browser_stack browser_path browser_busy - set w .browser[incr next_browser_id] + if {[winfo ismapped .]} { + set w .browser[incr next_browser_id] + set tl $w + toplevel $w + } else { + set w {} + set tl . + } set w_list $w.list.l set browser_commit($w_list) $commit set browser_status($w_list) {Starting...} @@ -3052,7 +3059,6 @@ proc new_browser {commit} { set browser_path($w_list) $browser_commit($w_list): set browser_busy($w_list) 1 - toplevel $w label $w.path -textvariable browser_path($w_list) \ -anchor w \ -justify left \ @@ -3102,8 +3108,8 @@ proc new_browser {commit} { bind $w_list break bind $w_list break - bind $w "focus $w" - bind $w " + bind $tl "focus $w" + bind $tl " array unset browser_buffer $w_list array unset browser_files $w_list array unset browser_status $w_list @@ -3112,7 +3118,7 @@ proc new_browser {commit} { array unset browser_commit $w_list array unset browser_busy $w_list " - wm title $w "[appname] ([reponame]): File Browser" + wm title $tl "[appname] ([reponame]): File Browser" ls_tree $w_list $browser_commit($w_list) {} } @@ -5019,6 +5025,7 @@ enable_option transport switch -- $subcommand { --version - version - +browser - blame { disable_option multicommit disable_option branch @@ -5359,6 +5366,15 @@ version { puts "git-gui version $appvers" exit } +browser { + if {[llength $argv] != 1} { + puts stderr "usage: $argv0 browser commit" + exit 1 + } + set current_branch [lindex $argv 0] + new_browser $current_branch + return +} blame { if {[llength $argv] != 2} { puts stderr "usage: $argv0 blame commit path" From dc7b24364dcbe9ab74d937c92af9999ac1a2db0b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 17 Feb 2007 13:12:52 -0800 Subject: [PATCH 014/201] Teach 'git apply' to look at $GIT_DIR/config When neither --index nor --cached was used, git-apply did not try calling setup_git_directory(), which means it did not look at configuration files at all. This fixes it to call the setup function but still allow the command to be run in a directory not controlled by git. The bug probably meant that 'git apply', not moving up to the toplevel, did not apply properly formatted diffs from the toplevel when you are inside a subdirectory, even though 'git apply --index' would. As a side effect, this patch fixes it as well. Signed-off-by: Junio C Hamano --- builtin-apply.c | 21 ++++++---- t/t4119-apply-config.sh | 90 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 8 deletions(-) create mode 100755 t/t4119-apply-config.sh diff --git a/builtin-apply.c b/builtin-apply.c index 3fefdacd94..fc1d6730d9 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -2595,9 +2595,18 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) int read_stdin = 1; int inaccurate_eof = 0; int errs = 0; + int is_not_gitdir = 0; const char *whitespace_option = NULL; + prefix = setup_git_directory_gently(&is_not_gitdir); + prefix_length = prefix ? strlen(prefix) : 0; + if (!is_not_gitdir) { + git_config(git_apply_config); + if (apply_default_whitespace) + parse_whitespace_option(apply_default_whitespace); + } + for (i = 1; i < argc; i++) { const char *arg = argv[i]; char *end; @@ -2648,10 +2657,14 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) continue; } if (!strcmp(arg, "--index")) { + if (is_not_gitdir) + die("--index outside a repository"); check_index = 1; continue; } if (!strcmp(arg, "--cached")) { + if (is_not_gitdir) + die("--cached outside a repository"); check_index = 1; cached = 1; continue; @@ -2700,14 +2713,6 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) inaccurate_eof = 1; continue; } - - if (check_index && prefix_length < 0) { - prefix = setup_git_directory(); - prefix_length = prefix ? strlen(prefix) : 0; - git_config(git_apply_config); - if (!whitespace_option && apply_default_whitespace) - parse_whitespace_option(apply_default_whitespace); - } if (0 < prefix_length) arg = prefix_filename(prefix, prefix_length, arg); diff --git a/t/t4119-apply-config.sh b/t/t4119-apply-config.sh new file mode 100755 index 0000000000..0e8ea7e2b8 --- /dev/null +++ b/t/t4119-apply-config.sh @@ -0,0 +1,90 @@ +#!/bin/sh +# +# Copyright (c) 2007 Junio C Hamano +# + +test_description='git-apply --whitespace=strip and configuration file. + +' + +. ./test-lib.sh + +test_expect_success setup ' + echo A >file1 && + cp file1 saved && + git add file1 && + echo "B " >file1 && + git diff >patch.file +' + +test_expect_success 'apply --whitespace=strip' ' + + cp saved file1 && + git update-index --refresh && + + git apply --whitespace=strip patch.file && + if grep " " file1 + then + echo "Eh?" + false + else + echo Happy + fi +' + +test_expect_success 'apply --whitespace=strip from config' ' + + cp saved file1 && + git update-index --refresh && + + git config apply.whitespace strip && + git apply patch.file && + if grep " " file1 + then + echo "Eh?" + false + else + echo Happy + fi +' + +mkdir sub +D=`pwd` + +test_expect_success 'apply --whitespace=strip in subdir' ' + + cd "$D" && + git config --unset-all apply.whitespace + cp saved file1 && + git update-index --refresh && + + cd sub && + git apply --whitespace=strip ../patch.file && + if grep " " ../file1 + then + echo "Eh?" + false + else + echo Happy + fi +' + +test_expect_success 'apply --whitespace=strip from config in subdir' ' + + cd "$D" && + git config apply.whitespace strip && + cp saved file1 && + git update-index --refresh && + + cd sub && + git apply ../patch.file && + if grep " " file1 + then + echo "Eh?" + false + else + echo Happy + fi +' + +test_done From 6716027108f426c83038b05baf3f20ceefe6fbd1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 17 Feb 2007 12:37:25 -0800 Subject: [PATCH 015/201] Teach core.autocrlf to 'git apply' This teaches git-apply that the data read from and written to the filesystem might need to get converted to adjust for local line-ending convention. Signed-off-by: Junio C Hamano --- builtin-apply.c | 34 ++++++++++++++++++++++++++++------ t/t0020-crlf.sh | 19 +++++++++++++++---- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/builtin-apply.c b/builtin-apply.c index 3fefdacd94..45c4acbd20 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -1393,28 +1393,39 @@ static void show_stats(struct patch *patch) free(qname); } -static int read_old_data(struct stat *st, const char *path, void *buf, unsigned long size) +static int read_old_data(struct stat *st, const char *path, char **buf_p, unsigned long *alloc_p, unsigned long *size_p) { int fd; unsigned long got; + unsigned long nsize; + char *nbuf; + unsigned long size = *size_p; + char *buf = *buf_p; switch (st->st_mode & S_IFMT) { case S_IFLNK: - return readlink(path, buf, size); + return readlink(path, buf, size) != size; case S_IFREG: fd = open(path, O_RDONLY); if (fd < 0) return error("unable to open %s", path); got = 0; for (;;) { - int ret = xread(fd, (char *) buf + got, size - got); + int ret = xread(fd, buf + got, size - got); if (ret <= 0) break; got += ret; } close(fd); - return got; - + nsize = got; + nbuf = buf; + if (convert_to_git(path, &nbuf, &nsize)) { + free(buf); + *buf_p = nbuf; + *alloc_p = nsize; + *size_p = nsize; + } + return got != size; default: return -1; } @@ -1910,7 +1921,7 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry * size = st->st_size; alloc = size + 8192; buf = xmalloc(alloc); - if (read_old_data(st, patch->old_name, buf, alloc) != size) + if (read_old_data(st, patch->old_name, &buf, &alloc, &size)) return error("read of %s failed", patch->old_name); } @@ -2282,12 +2293,22 @@ static void add_index_file(const char *path, unsigned mode, void *buf, unsigned static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size) { int fd; + char *nbuf; + unsigned long nsize; if (S_ISLNK(mode)) /* Although buf:size is counted string, it also is NUL * terminated. */ return symlink(buf, path); + nsize = size; + nbuf = (char *) buf; + if (convert_to_working_tree(path, &nbuf, &nsize)) { + free((char *) buf); + buf = nbuf; + size = nsize; + } + fd = open(path, O_CREAT | O_EXCL | O_WRONLY, (mode & 0100) ? 0777 : 0666); if (fd < 0) return -1; @@ -2598,6 +2619,7 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) const char *whitespace_option = NULL; + for (i = 1; i < argc; i++) { const char *arg = argv[i]; char *end; diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh index 58a4d86df3..723b29ad17 100755 --- a/t/t0020-crlf.sh +++ b/t/t0020-crlf.sh @@ -180,11 +180,8 @@ test_expect_success 'apply patch (autocrlf=true)' ' git repo-config core.autocrlf true && git read-tree --reset -u HEAD && - # Sore thumb - remove_cr one >tmp && mv -f tmp one && - git apply patch.file && - test "$patched" = "`git hash-object --stdin Date: Sun, 18 Feb 2007 02:12:32 -0500 Subject: [PATCH 016/201] git-gui: Correct crash when saving options in blame mode. Martin Waitz noticed that git-gui crashed while saving the user's options out if the application was started in blame mode. This was caused by the do_save_config procedure invoking reshow_diff incase the number of context lines was modified by the user. Because we bypassed main window UI setup to enter blame mode we did not set many of the globals which were accessed by reshow_diff, and reading unset variables is an error in Tcl. Aside from moving the globals to be set earlier, I also modified reshow_diff to not invoke clear_diff if there is no path currently in the diff viewer. This way reshow_diff does not crash when in blame mode due to the $ui_diff command not being defined. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index e7898014ac..551c11c9f5 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -359,6 +359,24 @@ set _reponame [lindex [file split \ [file normalize [file dirname $_gitdir]]] \ end] +###################################################################### +## +## global init + +set current_diff_path {} +set current_diff_side {} +set diff_actions [list] +set ui_status_value {Initializing...} + +set HEAD {} +set PARENT {} +set MERGE_HEAD [list] +set commit_type {} +set empty_tree {} +set current_branch {} +set current_diff_path {} +set selected_commit_type new + ###################################################################### ## ## task management @@ -682,8 +700,9 @@ proc reshow_diff {} { global current_diff_path current_diff_side set p $current_diff_path - if {$p eq {} - || $current_diff_side eq {} + if {$p eq {}} { + # No diff is being shown. + } elseif {$current_diff_side eq {} || [catch {set s $file_states($p)}] || [lsearch -sorted -exact $file_lists($current_diff_side) $p] == -1} { clear_diff @@ -5647,9 +5666,6 @@ bind_button3 $ui_comm "tk_popup $ctxm %X %Y" # -- Diff Header # -set current_diff_path {} -set current_diff_side {} -set diff_actions [list] proc trace_current_diff_path {varname args} { global current_diff_path diff_actions file_states if {$current_diff_path eq {}} { @@ -5842,7 +5858,6 @@ unset ui_diff_applyhunk # -- Status Bar # -set ui_status_value {Initializing...} label .status -textvariable ui_status_value \ -anchor w \ -justify left \ @@ -5916,15 +5931,6 @@ unset i set file_lists($ui_index) [list] set file_lists($ui_workdir) [list] -set HEAD {} -set PARENT {} -set MERGE_HEAD [list] -set commit_type {} -set empty_tree {} -set current_branch {} -set current_diff_path {} -set selected_commit_type new - wm title . "[appname] ([file normalize [file dirname [gitdir]]])" focus -force $ui_comm From 700ea47936e393a217f4174f80a19aa01f698b56 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 17 Feb 2007 18:12:46 -0800 Subject: [PATCH 017/201] Teach 'git apply' to look at $HOME/.gitconfig even outside of a repository Signed-off-by: Junio C Hamano --- builtin-apply.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/builtin-apply.c b/builtin-apply.c index fc1d6730d9..2784db2efb 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -2601,11 +2601,9 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) prefix = setup_git_directory_gently(&is_not_gitdir); prefix_length = prefix ? strlen(prefix) : 0; - if (!is_not_gitdir) { - git_config(git_apply_config); - if (apply_default_whitespace) - parse_whitespace_option(apply_default_whitespace); - } + git_config(git_apply_config); + if (apply_default_whitespace) + parse_whitespace_option(apply_default_whitespace); for (i = 1; i < argc; i++) { const char *arg = argv[i]; From c5a8c3ecd7fcec68b05b38640d4eac4e1cc7f50f Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 18 Feb 2007 17:27:24 +0100 Subject: [PATCH 018/201] diff --check: use colour Reuse the colour handling of the regular diff. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- diff.c | 57 +++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/diff.c b/diff.c index 13b9b6c560..d5caf6d603 100644 --- a/diff.c +++ b/diff.c @@ -398,22 +398,16 @@ static void emit_line(const char *set, const char *reset, const char *line, int puts(reset); } -static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len) +static void emit_line_with_ws(int nparents, + const char *set, const char *reset, const char *ws, + const char *line, int len) { - int col0 = ecbdata->nparents; + int col0 = nparents; int last_tab_in_indent = -1; int last_space_in_indent = -1; int i; int tail = len; int need_highlight_leading_space = 0; - const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE); - const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW); - - if (!*ws) { - emit_line(set, reset, line, len); - return; - } - /* The line is a newly added line. Does it have funny leading * whitespaces? In indent, SP should never precede a TAB. */ @@ -468,6 +462,18 @@ static void emit_add_line(const char *reset, struct emit_callback *ecbdata, cons emit_line(set, reset, line + i, len - i); } +static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len) +{ + const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE); + const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW); + + if (!*ws) + emit_line(set, reset, line, len); + else + emit_line_with_ws(ecbdata->nparents, set, reset, ws, + line, len); +} + static void fn_out_consume(void *priv, char *line, unsigned long len) { int i; @@ -870,30 +876,44 @@ static void show_numstat(struct diffstat_t* data, struct diff_options *options) struct checkdiff_t { struct xdiff_emit_state xm; const char *filename; - int lineno; + int lineno, color_diff; }; static void checkdiff_consume(void *priv, char *line, unsigned long len) { struct checkdiff_t *data = priv; + const char *ws = diff_get_color(data->color_diff, DIFF_WHITESPACE); + const char *reset = diff_get_color(data->color_diff, DIFF_RESET); + const char *set = diff_get_color(data->color_diff, DIFF_FILE_NEW); if (line[0] == '+') { - int i, spaces = 0; + int i, spaces = 0, space_before_tab = 0, white_space_at_end = 0; /* check space before tab */ for (i = 1; i < len && (line[i] == ' ' || line[i] == '\t'); i++) if (line[i] == ' ') spaces++; if (line[i - 1] == '\t' && spaces) - printf("%s:%d: space before tab:%.*s\n", - data->filename, data->lineno, (int)len, line); + space_before_tab = 1; /* check white space at line end */ if (line[len - 1] == '\n') len--; if (isspace(line[len - 1])) - printf("%s:%d: white space at end: %.*s\n", - data->filename, data->lineno, (int)len, line); + white_space_at_end = 1; + + if (space_before_tab || white_space_at_end) { + printf("%s:%d: %s", data->filename, data->lineno, ws); + if (space_before_tab) { + printf("space before tab"); + if (white_space_at_end) + putchar(','); + } + if (white_space_at_end) + printf("white space at end"); + printf(":%s ", reset); + emit_line_with_ws(1, set, reset, ws, line, len); + } data->lineno++; } else if (line[0] == ' ') @@ -1151,7 +1171,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b, static void builtin_checkdiff(const char *name_a, const char *name_b, struct diff_filespec *one, - struct diff_filespec *two) + struct diff_filespec *two, struct diff_options *o) { mmfile_t mf1, mf2; struct checkdiff_t data; @@ -1163,6 +1183,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, data.xm.consume = checkdiff_consume; data.filename = name_b ? name_b : name_a; data.lineno = 0; + data.color_diff = o->color_diff; if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) die("unable to read files to diff"); @@ -1773,7 +1794,7 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o) diff_fill_sha1_info(p->one); diff_fill_sha1_info(p->two); - builtin_checkdiff(name, other, p->one, p->two); + builtin_checkdiff(name, other, p->one, p->two, o); } void diff_setup(struct diff_options *options) From e63ccb84e3e29d428548669a7a1206d386a57d6d Mon Sep 17 00:00:00 2001 From: Fredrik Kuivinen Date: Sun, 18 Feb 2007 09:44:42 +0100 Subject: [PATCH 019/201] New autoconf test for iconv On a Solaris machine I have access to libc contains the symbol "iconv" but, when compiling with gcc and including iconv.h we get iconv.h from GNU libiconv. This header file define (among other things) "iconv" to "libiconv" and so on. In order to link with GNU libiconv we need -liconv. Currently we test if the symbol "iconv" is in libc (which is true), then we get a undefined reference error because we don't have libiconv_open. The solution this patch implements is to compile and link a small test program, instead of just checking if the libraries (libc and libiconv) contains the symbol "iconv". Signed-off-by: Fredrik Kuivinen Signed-off-by: Junio C Hamano --- configure.ac | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/configure.ac b/configure.ac index 7cfb3a0666..3a8e778def 100644 --- a/configure.ac +++ b/configure.ac @@ -114,13 +114,32 @@ AC_CHECK_LIB([expat], [XML_ParserCreate], [NO_EXPAT=YesPlease]) AC_SUBST(NO_EXPAT) # -# Define NEEDS_LIBICONV if linking with libc is not enough (Darwin). +# Define NEEDS_LIBICONV if linking with libc is not enough (Darwin and +# some Solaris installations). # Define NO_ICONV if neither libc nor libiconv support iconv. -AC_CHECK_LIB([c], [iconv], - [NEEDS_LIBICONV=], - AC_CHECK_LIB([iconv], [iconv], - [NEEDS_LIBICONV=YesPlease], - [NO_ICONV=YesPlease])) +AC_DEFUN([ICONVTEST_SRC], [ +#include + +int main(void) +{ + iconv_open("", ""); + return 0; +} +]) +AC_MSG_CHECKING([for iconv in -lc]) +AC_LINK_IFELSE(ICONVTEST_SRC, + [AC_MSG_RESULT([yes]) + NEEDS_LIBICONV=], + [AC_MSG_RESULT([no]) + old_LIBS="$LIBS" + LIBS="$LIBS -liconv" + AC_MSG_CHECKING([for iconv in -liconv]) + AC_LINK_IFELSE(ICONVTEST_SRC, + [AC_MSG_RESULT([yes]) + NEEDS_LIBICONV=YesPlease], + [AC_MSG_RESULT([no]) + NO_ICONV=YesPlease]) + LIBS="$old_LIBS"]) AC_SUBST(NEEDS_LIBICONV) AC_SUBST(NO_ICONV) test -n "$NEEDS_LIBICONV" && LIBS="$LIBS -liconv" From f496454e0fd22d003e9c9b5c3742b12d526bc0a4 Mon Sep 17 00:00:00 2001 From: Christian Schlotter Date: Mon, 19 Feb 2007 13:35:35 +0100 Subject: [PATCH 020/201] git-clone: Sync documentation to usage note. Documentation advertises the new `--depth ' parameter with an equal sign, while the usage notes (shown after `git-clone --help') do not. If I understood git-clone's source code correctly, the version without the equal sign is correct, which is why this patch syncs documentation to the usage note. Signed-off-by: Christian Schlotter Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.5.0.txt | 2 +- Documentation/git-clone.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/RelNotes-1.5.0.txt b/Documentation/RelNotes-1.5.0.txt index 599efb8c90..daf4bdb0d7 100644 --- a/Documentation/RelNotes-1.5.0.txt +++ b/Documentation/RelNotes-1.5.0.txt @@ -448,7 +448,7 @@ Updates in v1.5.0 since v1.4.4 series - There is a partial support for 'shallow' repositories that keeps only recent history. A 'shallow clone' is created by specifying how deep that truncated history should be - (e.g. "git clone --depth=5 git://some.where/repo.git"). + (e.g. "git clone --depth 5 git://some.where/repo.git"). Currently a shallow repository has number of limitations: diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index 707376f22c..6d32c491a5 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -11,7 +11,7 @@ SYNOPSIS [verse] 'git-clone' [--template=] [-l [-s]] [-q] [-n] [--bare] [-o ] [-u ] [--reference ] - [--depth=] [] + [--depth ] [] DESCRIPTION ----------- @@ -96,7 +96,7 @@ OPTIONS if unset the templates are taken from the installation defined default, typically `/usr/share/git-core/templates`. ---depth=:: +--depth :: Create a 'shallow' clone with a history truncated to the specified number of revs. A shallow repository has number of limitations (you cannot clone or fetch from From bc6b4f52fc79712db637ff90a472b82b32695e11 Mon Sep 17 00:00:00 2001 From: Jason Riedy Date: Mon, 19 Feb 2007 16:22:56 -0800 Subject: [PATCH 021/201] Add a compat/strtoumax.c for Solaris 8. Solaris 8 was pre-c99, and they weren't willing to commit to the strtoumax definition according to /usr/include/inttypes.h. This adds NO_STRTOUMAX and NO_STRTOULL for ancient systems. If NO_STRTOUMAX is defined, the routine in compat/strtoumax.c will be used instead. That routine passes its arguments to strtoull unless NO_STRTOULL is defined. If NO_STRTOULL, then the routine uses strtoul (unsigned long). Signed-off-by: Jason Riedy Acked-by: Shawn O Pearce Signed-off-by: Junio C Hamano --- Makefile | 13 +++++++++++++ compat/strtoumax.c | 10 ++++++++++ git-compat-util.h | 5 +++++ 3 files changed, 28 insertions(+) create mode 100644 compat/strtoumax.c diff --git a/Makefile b/Makefile index dae2919965..f85fb7c197 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,10 @@ all:: # # Define NO_STRLCPY if you don't have strlcpy. # +# Define NO_STRTOUMAX if you don't have strtoumax in the C library. +# If your compiler also does not support long long or does not have +# strtoull, define NO_STRTOULL. +# # Define NO_SETENV if you don't have setenv in the C library. # # Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link. @@ -353,11 +357,13 @@ ifeq ($(uname_S),SunOS) NO_UNSETENV = YesPlease NO_SETENV = YesPlease NO_C99_FORMAT = YesPlease + NO_STRTOUMAX = YesPlease endif ifeq ($(uname_R),5.9) NO_UNSETENV = YesPlease NO_SETENV = YesPlease NO_C99_FORMAT = YesPlease + NO_STRTOUMAX = YesPlease endif INSTALL = ginstall TAR = gtar @@ -517,6 +523,13 @@ ifdef NO_STRLCPY COMPAT_CFLAGS += -DNO_STRLCPY COMPAT_OBJS += compat/strlcpy.o endif +ifdef NO_STRTOUMAX + COMPAT_CFLAGS += -DNO_STRTOUMAX + COMPAT_OBJS += compat/strtoumax.o +endif +ifdef NO_STRTOULL + COMPAT_CFLAGS += -DNO_STRTOULL +endif ifdef NO_SETENV COMPAT_CFLAGS += -DNO_SETENV COMPAT_OBJS += compat/setenv.o diff --git a/compat/strtoumax.c b/compat/strtoumax.c new file mode 100644 index 0000000000..5541353a77 --- /dev/null +++ b/compat/strtoumax.c @@ -0,0 +1,10 @@ +#include "../git-compat-util.h" + +uintmax_t gitstrtoumax (const char *nptr, char **endptr, int base) +{ +#if defined(NO_STRTOULL) + return strtoul(nptr, endptr, base); +#else + return strtoull(nptr, endptr, base); +#endif +} diff --git a/git-compat-util.h b/git-compat-util.h index 105ac28f97..9863cf671f 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -139,6 +139,11 @@ extern char *gitstrcasestr(const char *haystack, const char *needle); extern size_t gitstrlcpy(char *, const char *, size_t); #endif +#ifdef NO_STRTOUMAX +#define strtoumax gitstrtoumax +extern uintmax_t gitstrtoumax(const char *, char **, int); +#endif + extern void release_pack_memory(size_t); static inline char* xstrdup(const char *str) From e326bce65c2a8b10f40c1869e13c7450a243b109 Mon Sep 17 00:00:00 2001 From: Jason Riedy Date: Mon, 19 Feb 2007 16:27:09 -0800 Subject: [PATCH 022/201] Obey NO_C99_FORMAT in fast-import.c. Define UM_FMT and UM10_FMT and use in place of %ju and %10ju, respectively. Both format as unsigned long long, so this assumes the compiler supports long long. Signed-off-by: Jason Riedy Signed-off-by: Junio C Hamano --- fast-import.c | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/fast-import.c b/fast-import.c index fd3b117574..8720090a3e 100644 --- a/fast-import.c +++ b/fast-import.c @@ -133,6 +133,15 @@ Format of STDIN stream: #define PACK_ID_BITS 16 #define MAX_PACK_ID ((1<data.marked[idnum]; } if (!oe) - die("mark :%ju not declared", orig_idnum); + die("mark :" UM_FMT " not declared", orig_idnum); return oe; } @@ -1361,7 +1370,7 @@ static void dump_marks_helper(FILE *f, } else { for (k = 0; k < 1024; k++) { if (m->data.marked[k]) - fprintf(f, ":%ju %s\n", base + k, + fprintf(f, ":" UM_FMT " %s\n", base + k, sha1_to_hex(m->data.marked[k]->sha1)); } } @@ -1687,7 +1696,7 @@ static void cmd_from(struct branch *b) unsigned long size; char *buf; if (oe->type != OBJ_COMMIT) - die("Mark :%ju not a commit", idnum); + die("Mark :" UM_FMT " not a commit", idnum); hashcpy(b->sha1, oe->sha1); buf = gfi_unpack_entry(oe, &size); if (!buf || size < 46) @@ -1740,7 +1749,7 @@ static struct hash_list *cmd_merge(unsigned int *count) uintmax_t idnum = strtoumax(from + 1, NULL, 10); struct object_entry *oe = find_mark(idnum); if (oe->type != OBJ_COMMIT) - die("Mark :%ju not a commit", idnum); + die("Mark :" UM_FMT " not a commit", idnum); hashcpy(n->sha1, oe->sha1); } else if (get_sha1(from, n->sha1)) die("Invalid ref name or SHA1 expression: %s", from); @@ -1884,7 +1893,7 @@ static void cmd_new_tag(void) from_mark = strtoumax(from + 1, NULL, 10); oe = find_mark(from_mark); if (oe->type != OBJ_COMMIT) - die("Mark :%ju not a commit", from_mark); + die("Mark :" UM_FMT " not a commit", from_mark); hashcpy(sha1, oe->sha1); } else if (!get_sha1(from, sha1)) { unsigned long size; @@ -2059,18 +2068,18 @@ int main(int argc, const char **argv) fprintf(stderr, "%s statistics:\n", argv[0]); fprintf(stderr, "---------------------------------------------------------------------\n"); - fprintf(stderr, "Alloc'd objects: %10ju\n", alloc_count); - fprintf(stderr, "Total objects: %10ju (%10ju duplicates )\n", total_count, duplicate_count); - fprintf(stderr, " blobs : %10ju (%10ju duplicates %10ju deltas)\n", object_count_by_type[OBJ_BLOB], duplicate_count_by_type[OBJ_BLOB], delta_count_by_type[OBJ_BLOB]); - fprintf(stderr, " trees : %10ju (%10ju duplicates %10ju deltas)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE], delta_count_by_type[OBJ_TREE]); - fprintf(stderr, " commits: %10ju (%10ju duplicates %10ju deltas)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT], delta_count_by_type[OBJ_COMMIT]); - fprintf(stderr, " tags : %10ju (%10ju duplicates %10ju deltas)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG], delta_count_by_type[OBJ_TAG]); + fprintf(stderr, "Alloc'd objects: " UM10_FMT "\n", alloc_count); + fprintf(stderr, "Total objects: " UM10_FMT " (" UM10_FMT " duplicates )\n", total_count, duplicate_count); + fprintf(stderr, " blobs : " UM10_FMT " (" UM10_FMT " duplicates " UM10_FMT " deltas)\n", object_count_by_type[OBJ_BLOB], duplicate_count_by_type[OBJ_BLOB], delta_count_by_type[OBJ_BLOB]); + fprintf(stderr, " trees : " UM10_FMT " (" UM10_FMT " duplicates " UM10_FMT " deltas)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE], delta_count_by_type[OBJ_TREE]); + fprintf(stderr, " commits: " UM10_FMT " (" UM10_FMT " duplicates " UM10_FMT " deltas)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT], delta_count_by_type[OBJ_COMMIT]); + fprintf(stderr, " tags : " UM10_FMT " (" UM10_FMT " duplicates " UM10_FMT " deltas)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG], delta_count_by_type[OBJ_TAG]); fprintf(stderr, "Total branches: %10lu (%10lu loads )\n", branch_count, branch_load_count); - fprintf(stderr, " marks: %10ju (%10ju unique )\n", (((uintmax_t)1) << marks->shift) * 1024, marks_set_count); + fprintf(stderr, " marks: " UM10_FMT " (" UM10_FMT " unique )\n", (((uintmax_t)1) << marks->shift) * 1024, marks_set_count); fprintf(stderr, " atoms: %10u\n", atom_cnt); - fprintf(stderr, "Memory total: %10ju KiB\n", (total_allocd + alloc_count*sizeof(struct object_entry))/1024); + fprintf(stderr, "Memory total: " UM10_FMT " KiB\n", (total_allocd + alloc_count*sizeof(struct object_entry))/1024); fprintf(stderr, " pools: %10lu KiB\n", (unsigned long)(total_allocd/1024)); - fprintf(stderr, " objects: %10ju KiB\n", (alloc_count*sizeof(struct object_entry))/1024); + fprintf(stderr, " objects: " UM10_FMT " KiB\n", (alloc_count*sizeof(struct object_entry))/1024); fprintf(stderr, "---------------------------------------------------------------------\n"); pack_report(); fprintf(stderr, "---------------------------------------------------------------------\n"); From 1e592d65b50dbec87fde9f4ef1b7fd8d90bf7b8c Mon Sep 17 00:00:00 2001 From: Theodore Ts'o Date: Sun, 18 Feb 2007 23:00:00 -0500 Subject: [PATCH 023/201] Teach git-remote to update existing remotes by fetching from them This allows users to use the command "git remote update" to update all remotes that are being tracked in the repository. Signed-off-by: "Theodore Ts'o" Signed-off-by: Junio C Hamano --- Documentation/config.txt | 4 ++++ Documentation/git-remote.txt | 10 +++++++++- git-remote.perl | 13 +++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 38655350f2..d8e696f4cd 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -439,6 +439,10 @@ pull.octopus:: pull.twohead:: The default merge strategy to use when pulling a single branch. +remote.fetch:: + The list of remotes which are fetched by "git remote update". + See gitlink:git-remote[1]. + remote..url:: The URL of a remote repository. See gitlink:git-fetch[1] or gitlink:git-push[1]. diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index a60c31a315..06ba2e6f26 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -13,6 +13,7 @@ SYNOPSIS 'git-remote' add 'git-remote' show 'git-remote' prune +'git-remote' update DESCRIPTION ----------- @@ -40,7 +41,14 @@ Gives some information about the remote . Deletes all stale tracking branches under . These stale branches have already been removed from the remote repository -referenced by , but are still locally available in "remotes/". +referenced by , but are still locally available in +"remotes/". + +'update':: + +Fetch updates for the remotes in the repository. By default all remotes +are updated, but this can be configured via the configuration parameter +'remote.fetch'. (See gitlink:git-config[1]). DISCUSSION diff --git a/git-remote.perl b/git-remote.perl index c56c5a84a4..6e473ecfd0 100755 --- a/git-remote.perl +++ b/git-remote.perl @@ -303,6 +303,18 @@ sub add_usage { show_remote($ARGV[$i], $ls_remote); } } +elsif ($ARGV[0] eq 'update') { + my $conf = $git->config("remote.fetch"); + if (defined($conf)) { + @remotes = split(' ', $conf); + } else { + @remotes = sort keys %$remote; + } + for (@remotes) { + print "Fetching $_\n"; + $git->command('fetch', "$_"); + } +} elsif ($ARGV[0] eq 'prune') { my $ls_remote = 1; my $i; @@ -360,5 +372,6 @@ sub add_usage { print STDERR " git remote add \n"; print STDERR " git remote show \n"; print STDERR " git remote prune \n"; + print STDERR " git remote update\n"; exit(1); } From aea1945744214bf84908586af8be1c098a6f346d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 19 Feb 2007 17:58:58 -0800 Subject: [PATCH 024/201] git-apply: do not lose cwd when run from a subdirectory. When a patch modifies (not deletes) the last file in a directory, because we treat a modification just as deletion followed by creation, and deleting the last file in a directory automatically rmdir(2)'s that directory, we ended up removing the directory, which can potentially be the cwd, and then recreating the same directory to create the patch result. Avoid the rmdir step when remove_file() is called only because we are replacing it with the result by later calling create_file(). Signed-off-by: Junio C Hamano --- builtin-apply.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/builtin-apply.c b/builtin-apply.c index 2784db2efb..3f829fb661 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -2232,7 +2232,7 @@ static void patch_stats(struct patch *patch) } } -static void remove_file(struct patch *patch) +static void remove_file(struct patch *patch, int rmdir_empty) { if (write_index) { if (remove_file_from_cache(patch->old_name) < 0) @@ -2240,7 +2240,7 @@ static void remove_file(struct patch *patch) cache_tree_invalidate_path(active_cache_tree, patch->old_name); } if (!cached) { - if (!unlink(patch->old_name)) { + if (!unlink(patch->old_name) && rmdir_empty) { char *name = xstrdup(patch->old_name); char *end = strrchr(name, '/'); while (end) { @@ -2373,7 +2373,7 @@ static void write_out_one_result(struct patch *patch, int phase) { if (patch->is_delete > 0) { if (phase == 0) - remove_file(patch); + remove_file(patch, 1); return; } if (patch->is_new > 0 || patch->is_copy) { @@ -2386,7 +2386,7 @@ static void write_out_one_result(struct patch *patch, int phase) * thing: remove the old, write the new */ if (phase == 0) - remove_file(patch); + remove_file(patch, 0); if (phase == 1) create_file(patch); } From 56185f49d03cae28048146e902089ea366c6cd6c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 19 Feb 2007 17:57:29 -0800 Subject: [PATCH 025/201] git-apply: require -p when working in a subdirectory. git-apply running inside a subdirectory, with or without --index, used to always assume that the patch is formatted in such a way to apply with -p1 from the toplevel, but it is more useful and consistent with the use of "GNU patch -p1" if it defaulted to assume that its input is meant to apply at the level it is invoked in. This changes the behaviour. It used to be that the patch generated this way would apply without any trick: edit Documentation/Makefile git diff >patch.file cd Documentation git apply ../patch.file You need to give an explicit -p2 to git-apply now. On the other hand, if you got a patch from somebody else who did not follow "patch is to apply from the top with -p1" convention, the input patch would start with: diff -u Makefile.old Makefile --- Makefile.old +++ Makefile and in such a case, you can apply it with: git apply -p0 patch.file Signed-off-by: Junio C Hamano --- builtin-apply.c | 42 +++++++++++++++++++++++++++-------------- t/t4119-apply-config.sh | 32 +++++++++++++++++-------------- 2 files changed, 46 insertions(+), 28 deletions(-) diff --git a/builtin-apply.c b/builtin-apply.c index 3f829fb661..053511e2ee 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -238,7 +238,7 @@ static int name_terminate(const char *name, int namelen, int c, int terminate) return 1; } -static char * find_name(const char *line, char *def, int p_value, int terminate) +static char *find_name(const char *line, char *def, int p_value, int terminate) { int len; const char *start = line; @@ -362,7 +362,7 @@ static int gitdiff_hdrend(const char *line, struct patch *patch) static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew) { if (!orig_name && !isnull) - return find_name(line, NULL, 1, TERM_TAB); + return find_name(line, NULL, p_value, TERM_TAB); if (orig_name) { int len; @@ -372,7 +372,7 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, len = strlen(name); if (isnull) die("git-apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr); - another = find_name(line, NULL, 1, TERM_TAB); + another = find_name(line, NULL, p_value, TERM_TAB); if (!another || memcmp(another, name, len)) die("git-apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr); free(another); @@ -427,28 +427,28 @@ static int gitdiff_newfile(const char *line, struct patch *patch) static int gitdiff_copysrc(const char *line, struct patch *patch) { patch->is_copy = 1; - patch->old_name = find_name(line, NULL, 0, 0); + patch->old_name = find_name(line, NULL, p_value-1, 0); return 0; } static int gitdiff_copydst(const char *line, struct patch *patch) { patch->is_copy = 1; - patch->new_name = find_name(line, NULL, 0, 0); + patch->new_name = find_name(line, NULL, p_value-1, 0); return 0; } static int gitdiff_renamesrc(const char *line, struct patch *patch) { patch->is_rename = 1; - patch->old_name = find_name(line, NULL, 0, 0); + patch->old_name = find_name(line, NULL, p_value-1, 0); return 0; } static int gitdiff_renamedst(const char *line, struct patch *patch) { patch->is_rename = 1; - patch->new_name = find_name(line, NULL, 0, 0); + patch->new_name = find_name(line, NULL, p_value-1, 0); return 0; } @@ -2499,15 +2499,26 @@ static int use_patch(struct patch *p) return 0; x = x->next; } - if (0 < prefix_length) { - int pathlen = strlen(pathname); - if (pathlen <= prefix_length || - memcmp(prefix, pathname, prefix_length)) - return 0; - } return 1; } +static char *prefix_one(char *name) +{ + if (!name) + return name; + return xstrdup(prefix_filename(prefix, prefix_length, name)); +} + +static void prefix_patches(struct patch *p) +{ + if (!prefix) + return; + for ( ; p; p = p->next) { + p->new_name = prefix_one(p->new_name); + p->old_name = prefix_one(p->old_name); + } +} + static int apply_patch(int fd, const char *filename, int inaccurate_eof) { unsigned long offset, size; @@ -2530,11 +2541,14 @@ static int apply_patch(int fd, const char *filename, int inaccurate_eof) break; if (apply_in_reverse) reverse_patches(patch); + if (prefix) + prefix_patches(patch); if (use_patch(patch)) { patch_stats(patch); *listp = patch; listp = &patch->next; - } else { + } + else { /* perhaps free it a bit better? */ free(patch); skipped_patch++; diff --git a/t/t4119-apply-config.sh b/t/t4119-apply-config.sh index 0e8ea7e2b8..816b5b8fb2 100755 --- a/t/t4119-apply-config.sh +++ b/t/t4119-apply-config.sh @@ -10,20 +10,22 @@ test_description='git-apply --whitespace=strip and configuration file. . ./test-lib.sh test_expect_success setup ' - echo A >file1 && - cp file1 saved && - git add file1 && - echo "B " >file1 && + mkdir sub && + echo A >sub/file1 && + cp sub/file1 saved && + git add sub/file1 && + echo "B " >sub/file1 && git diff >patch.file ' test_expect_success 'apply --whitespace=strip' ' - cp saved file1 && + rm -f sub/file1 && + cp saved sub/file1 && git update-index --refresh && git apply --whitespace=strip patch.file && - if grep " " file1 + if grep " " sub/file1 then echo "Eh?" false @@ -34,12 +36,13 @@ test_expect_success 'apply --whitespace=strip' ' test_expect_success 'apply --whitespace=strip from config' ' - cp saved file1 && + rm -f sub/file1 && + cp saved sub/file1 && git update-index --refresh && git config apply.whitespace strip && git apply patch.file && - if grep " " file1 + if grep " " sub/file1 then echo "Eh?" false @@ -48,19 +51,19 @@ test_expect_success 'apply --whitespace=strip from config' ' fi ' -mkdir sub D=`pwd` test_expect_success 'apply --whitespace=strip in subdir' ' cd "$D" && git config --unset-all apply.whitespace - cp saved file1 && + rm -f sub/file1 && + cp saved sub/file1 && git update-index --refresh && cd sub && - git apply --whitespace=strip ../patch.file && - if grep " " ../file1 + git apply --whitespace=strip -p2 ../patch.file && + if grep " " file1 then echo "Eh?" false @@ -73,11 +76,12 @@ test_expect_success 'apply --whitespace=strip from config in subdir' ' cd "$D" && git config apply.whitespace strip && - cp saved file1 && + rm -f sub/file1 && + cp saved sub/file1 && git update-index --refresh && cd sub && - git apply ../patch.file && + git apply -p2 ../patch.file && if grep " " file1 then echo "Eh?" From eac70c4f64a618744e05d4a5be61a356c0011033 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 20 Feb 2007 03:45:49 +0100 Subject: [PATCH 026/201] apply: fix memory leak in prefix_one() Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin-apply.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/builtin-apply.c b/builtin-apply.c index 053511e2ee..2a40af3ff0 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -2502,11 +2502,13 @@ static int use_patch(struct patch *p) return 1; } -static char *prefix_one(char *name) +static void prefix_one(char **name) { - if (!name) - return name; - return xstrdup(prefix_filename(prefix, prefix_length, name)); + char *old_name = *name; + if (!old_name) + return; + *name = xstrdup(prefix_filename(prefix, prefix_length, *name)); + free(old_name); } static void prefix_patches(struct patch *p) @@ -2514,8 +2516,9 @@ static void prefix_patches(struct patch *p) if (!prefix) return; for ( ; p; p = p->next) { - p->new_name = prefix_one(p->new_name); - p->old_name = prefix_one(p->old_name); + if (p->new_name != p->old_name) + prefix_one(&p->new_name); + prefix_one(&p->old_name); } } From 59d3f541cfe48345d708330bc19d8a880c80c595 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 20 Feb 2007 01:08:48 +0100 Subject: [PATCH 027/201] name-rev: avoid "^0" when unneeded When naming by a tag, we used to add "^0" even if this was not really necessary. For example, `git name-rev de6f0def` now outputs de6f0def tags/v1.5.0.1~9 instead of de6f0def tags/v1.5.0.1^0~9 Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin-name-rev.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/builtin-name-rev.c b/builtin-name-rev.c index b4f15cc38a..1b06b4afaa 100644 --- a/builtin-name-rev.c +++ b/builtin-name-rev.c @@ -57,13 +57,17 @@ static void name_rev(struct commit *commit, parents; parents = parents->next, parent_number++) { if (parent_number > 1) { - char *new_name = xmalloc(strlen(tip_name)+8); + int len = strlen(tip_name); + char *new_name = xmalloc(len + 8); + if (len > 2 && !strcmp(tip_name + len - 2, "^0")) + len -= 2; if (generation > 0) - sprintf(new_name, "%s~%d^%d", tip_name, + sprintf(new_name, "%.*s~%d^%d", len, tip_name, generation, parent_number); else - sprintf(new_name, "%s^%d", tip_name, parent_number); + sprintf(new_name, "%.*s^%d", len, tip_name, + parent_number); name_rev(parents->item, new_name, merge_traversals + 1 , 0, 0); @@ -119,10 +123,15 @@ static const char* get_rev_name(struct object *o) if (!n->generation) return n->tip_name; + else { + int len = strlen(n->tip_name); + if (len > 2 && !strcmp(n->tip_name + len - 2, "^0")) + len -= 2; + snprintf(buffer, sizeof(buffer), "%.*s~%d", len, n->tip_name, + n->generation); - snprintf(buffer, sizeof(buffer), "%s~%d", n->tip_name, n->generation); - - return buffer; + return buffer; + } } int cmd_name_rev(int argc, const char **argv, const char *prefix) From c4025103faf02a1b457b945cf5e4e12c97fa72d7 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 19 Feb 2007 03:14:59 +0100 Subject: [PATCH 028/201] rev-list --max-age, --max-count: support --boundary Now, when saying --max-age=, or --max-count=, together with --boundary, rev-list prints the boundary commits, i.e. the commits which are _just_ not shown without --boundary, i.e. their children are, but they aren't. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- revision.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/revision.c b/revision.c index 15bdaf6095..4f298bacc7 100644 --- a/revision.c +++ b/revision.c @@ -1229,9 +1229,15 @@ static struct commit *get_revision_1(struct rev_info *revs) */ if (!revs->limited) { if (revs->max_age != -1 && - (commit->date < revs->max_age)) - continue; - add_parents_to_list(revs, commit, &revs->commits); + (commit->date < revs->max_age)) { + if (revs->boundary) + commit->object.flags |= + BOUNDARY_SHOW | BOUNDARY; + else + continue; + } else + add_parents_to_list(revs, commit, + &revs->commits); } if (commit->object.flags & SHOWN) continue; @@ -1298,7 +1304,18 @@ struct commit *get_revision(struct rev_info *revs) case -1: break; case 0: - return NULL; + if (revs->boundary) { + struct commit_list *list = revs->commits; + while (list) { + list->item->object.flags |= + BOUNDARY_SHOW | BOUNDARY; + list = list->next; + } + /* all remaining commits are boundary commits */ + revs->max_count = -1; + revs->limited = 1; + } else + return NULL; default: revs->max_count--; } From 32043c9f8c60fc03b0b6a324c559d98094729323 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 14 Feb 2007 12:48:14 +0100 Subject: [PATCH 029/201] config: read system-wide defaults from /etc/gitconfig The settings in /etc/gitconfig can be overridden in ~/.gitconfig, which in turn can be overridden in .git/config. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- Documentation/config.txt | 3 ++- Makefile | 5 ++++- builtin-config.c | 15 ++++++++++++--- config.c | 2 ++ 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 38655350f2..1dd90d8046 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -5,7 +5,8 @@ The git configuration file contains a number of variables that affect the git command's behavior. `.git/config` file for each repository is used to store the information for that repository, and `$HOME/.gitconfig` is used to store per user information to give -fallback values for `.git/config` file. +fallback values for `.git/config` file. The file `/etc/gitconfig` +can be used to store system-wide defaults. They can be used by both the git plumbing and the porcelains. The variables are divided into sections, where diff --git a/Makefile b/Makefile index 40bdcff696..203aac4517 100644 --- a/Makefile +++ b/Makefile @@ -124,6 +124,7 @@ prefix = $(HOME) bindir = $(prefix)/bin gitexecdir = $(bindir) template_dir = $(prefix)/share/git-core/templates/ +ETC_GITCONFIG = $(prefix)/etc/gitconfig # DESTDIR= # default configuration for gitweb @@ -584,6 +585,7 @@ endif # Shell quote (do not use $(call) to accommodate ancient setups); SHA1_HEADER_SQ = $(subst ','\'',$(SHA1_HEADER)) +ETC_GITCONFIG_SQ = $(subst ','\'',$(ETC_GITCONFIG)) DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) bindir_SQ = $(subst ','\'',$(bindir)) @@ -596,7 +598,8 @@ PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) LIBS = $(GITLIBS) $(EXTLIBS) -BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' $(COMPAT_CFLAGS) +BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' \ + -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' $(COMPAT_CFLAGS) LIB_OBJS += $(COMPAT_OBJS) ALL_CFLAGS += $(BASIC_CFLAGS) diff --git a/builtin-config.c b/builtin-config.c index 0f9051da17..f1433a4ab6 100644 --- a/builtin-config.c +++ b/builtin-config.c @@ -64,7 +64,7 @@ static int get_value(const char* key_, const char* regex_) int ret = -1; char *tl; char *global = NULL, *repo_config = NULL; - const char *local; + const char *system_wide = NULL, *local; local = getenv(CONFIG_ENVIRONMENT); if (!local) { @@ -74,6 +74,7 @@ static int get_value(const char* key_, const char* regex_) local = repo_config = xstrdup(git_path("config")); if (home) global = xstrdup(mkpath("%s/.gitconfig", home)); + system_wide = ETC_GITCONFIG; } key = xstrdup(key_); @@ -103,11 +104,15 @@ static int get_value(const char* key_, const char* regex_) } } + if (do_all && system_wide) + git_config_from_file(show_config, system_wide); if (do_all && global) git_config_from_file(show_config, global); git_config_from_file(show_config, local); if (!do_all && !seen && global) git_config_from_file(show_config, global); + if (!do_all && !seen && system_wide) + git_config_from_file(show_config, system_wide); free(key); if (regexp) { @@ -147,7 +152,10 @@ int cmd_config(int argc, const char **argv, const char *prefix) } else { die("$HOME not set"); } - } else if (!strcmp(argv[1], "--rename-section")) { + } + else if (!strcmp(argv[1], "--system")) + setenv("GIT_CONFIG", ETC_GITCONFIG, 1); + else if (!strcmp(argv[1], "--rename-section")) { int ret; if (argc != 4) usage(git_config_set_usage); @@ -159,7 +167,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) return 1; } return 0; - } else + } + else break; argc--; argv++; diff --git a/config.c b/config.c index d82107124a..b0c0948cc8 100644 --- a/config.c +++ b/config.c @@ -383,6 +383,8 @@ int git_config(config_fn_t fn) * config file otherwise. */ filename = getenv(CONFIG_ENVIRONMENT); if (!filename) { + if (!access(ETC_GITCONFIG, R_OK)) + ret += git_config_from_file(fn, ETC_GITCONFIG); home = getenv("HOME"); filename = getenv(CONFIG_LOCAL_ENVIRONMENT); if (!filename) From a53b12c3a22b62c3b47c527f6641bb5ceceb0c1b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 20 Feb 2007 00:44:35 -0800 Subject: [PATCH 030/201] Link 1.5.0.1 documentation from the main page. Signed-off-by: Junio C Hamano --- Documentation/git.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/git.txt b/Documentation/git.txt index 3d8be5931c..aa3acc0466 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -35,6 +35,10 @@ ifdef::stalenotes[] You are reading the documentation for the latest version of git. Documentation for older releases are available here: +* link:v1.5.0.1/git.html[documentation for release 1.5.0.1] + +* link:v1.5.0.1/RelNotes-1.5.0.1.txt[release notes for 1.5.0.1] + * link:v1.5.0/git.html[documentation for release 1.5.0] * link:v1.5.0/RelNotes-1.5.0.txt[release notes for 1.5.0] From 3efb1f343a9d6214a59a814029cab96638e9b915 Mon Sep 17 00:00:00 2001 From: Jason Riedy Date: Tue, 20 Feb 2007 17:34:56 -0800 Subject: [PATCH 031/201] Check for PRIuMAX rather than NO_C99_FORMAT in fast-import.c. Thanks to Simon 'corecode' Schubert for the clean-up. Defining the C99 standard PRIuMAX when necessary replaces UM_FMT and the awkward UM10_FMT. There are no direct C99 translations for other uses of NO_C99_FORMAT in git, alas. Signed-off-by: Jason Riedy Signed-off-by: Junio C Hamano --- fast-import.c | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/fast-import.c b/fast-import.c index 8720090a3e..1ae125a040 100644 --- a/fast-import.c +++ b/fast-import.c @@ -133,13 +133,8 @@ Format of STDIN stream: #define PACK_ID_BITS 16 #define MAX_PACK_ID ((1<data.marked[idnum]; } if (!oe) - die("mark :" UM_FMT " not declared", orig_idnum); + die("mark :%" PRIuMAX " not declared", orig_idnum); return oe; } @@ -1370,7 +1365,7 @@ static void dump_marks_helper(FILE *f, } else { for (k = 0; k < 1024; k++) { if (m->data.marked[k]) - fprintf(f, ":" UM_FMT " %s\n", base + k, + fprintf(f, ":%" PRIuMAX " %s\n", base + k, sha1_to_hex(m->data.marked[k]->sha1)); } } @@ -1696,7 +1691,7 @@ static void cmd_from(struct branch *b) unsigned long size; char *buf; if (oe->type != OBJ_COMMIT) - die("Mark :" UM_FMT " not a commit", idnum); + die("Mark :%" PRIuMAX " not a commit", idnum); hashcpy(b->sha1, oe->sha1); buf = gfi_unpack_entry(oe, &size); if (!buf || size < 46) @@ -1749,7 +1744,7 @@ static struct hash_list *cmd_merge(unsigned int *count) uintmax_t idnum = strtoumax(from + 1, NULL, 10); struct object_entry *oe = find_mark(idnum); if (oe->type != OBJ_COMMIT) - die("Mark :" UM_FMT " not a commit", idnum); + die("Mark :%" PRIuMAX " not a commit", idnum); hashcpy(n->sha1, oe->sha1); } else if (get_sha1(from, n->sha1)) die("Invalid ref name or SHA1 expression: %s", from); @@ -1893,7 +1888,7 @@ static void cmd_new_tag(void) from_mark = strtoumax(from + 1, NULL, 10); oe = find_mark(from_mark); if (oe->type != OBJ_COMMIT) - die("Mark :" UM_FMT " not a commit", from_mark); + die("Mark :%" PRIuMAX " not a commit", from_mark); hashcpy(sha1, oe->sha1); } else if (!get_sha1(from, sha1)) { unsigned long size; @@ -2068,18 +2063,18 @@ int main(int argc, const char **argv) fprintf(stderr, "%s statistics:\n", argv[0]); fprintf(stderr, "---------------------------------------------------------------------\n"); - fprintf(stderr, "Alloc'd objects: " UM10_FMT "\n", alloc_count); - fprintf(stderr, "Total objects: " UM10_FMT " (" UM10_FMT " duplicates )\n", total_count, duplicate_count); - fprintf(stderr, " blobs : " UM10_FMT " (" UM10_FMT " duplicates " UM10_FMT " deltas)\n", object_count_by_type[OBJ_BLOB], duplicate_count_by_type[OBJ_BLOB], delta_count_by_type[OBJ_BLOB]); - fprintf(stderr, " trees : " UM10_FMT " (" UM10_FMT " duplicates " UM10_FMT " deltas)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE], delta_count_by_type[OBJ_TREE]); - fprintf(stderr, " commits: " UM10_FMT " (" UM10_FMT " duplicates " UM10_FMT " deltas)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT], delta_count_by_type[OBJ_COMMIT]); - fprintf(stderr, " tags : " UM10_FMT " (" UM10_FMT " duplicates " UM10_FMT " deltas)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG], delta_count_by_type[OBJ_TAG]); + fprintf(stderr, "Alloc'd objects: %10" PRIuMAX "\n", alloc_count); + fprintf(stderr, "Total objects: %10" PRIuMAX " (%10" PRIuMAX " duplicates )\n", total_count, duplicate_count); + fprintf(stderr, " blobs : %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_BLOB], duplicate_count_by_type[OBJ_BLOB], delta_count_by_type[OBJ_BLOB]); + fprintf(stderr, " trees : %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE], delta_count_by_type[OBJ_TREE]); + fprintf(stderr, " commits: %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT], delta_count_by_type[OBJ_COMMIT]); + fprintf(stderr, " tags : %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG], delta_count_by_type[OBJ_TAG]); fprintf(stderr, "Total branches: %10lu (%10lu loads )\n", branch_count, branch_load_count); - fprintf(stderr, " marks: " UM10_FMT " (" UM10_FMT " unique )\n", (((uintmax_t)1) << marks->shift) * 1024, marks_set_count); + fprintf(stderr, " marks: %10" PRIuMAX " (%10" PRIuMAX " unique )\n", (((uintmax_t)1) << marks->shift) * 1024, marks_set_count); fprintf(stderr, " atoms: %10u\n", atom_cnt); - fprintf(stderr, "Memory total: " UM10_FMT " KiB\n", (total_allocd + alloc_count*sizeof(struct object_entry))/1024); + fprintf(stderr, "Memory total: %10" PRIuMAX " KiB\n", (total_allocd + alloc_count*sizeof(struct object_entry))/1024); fprintf(stderr, " pools: %10lu KiB\n", (unsigned long)(total_allocd/1024)); - fprintf(stderr, " objects: " UM10_FMT " KiB\n", (alloc_count*sizeof(struct object_entry))/1024); + fprintf(stderr, " objects: %10" PRIuMAX " KiB\n", (alloc_count*sizeof(struct object_entry))/1024); fprintf(stderr, "---------------------------------------------------------------------\n"); pack_report(); fprintf(stderr, "---------------------------------------------------------------------\n"); From ee40599330e75d9266672ed5031b3f75764fea3d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 18 Feb 2007 19:06:09 -0500 Subject: [PATCH 032/201] git-gui: Use mixed path for docs on Cygwin. The Firefox browser requires that a URL use / to delimit directories. This is instead of \, as \ gets escaped by the browser into its hex escape code and then relative URLs are incorrectly resolved, Firefox no longer sees the directories for what they are. Since we are handing the browser a true URL, we better use the standard / for directories. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-gui.sh b/git-gui.sh index 551c11c9f5..63848dc705 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -5336,7 +5336,7 @@ set doc_path [file dirname [gitexec]] set doc_path [file join $doc_path Documentation index.html] if {[is_Cygwin]} { - set doc_path [exec cygpath --windows $doc_path] + set doc_path [exec cygpath --mixed $doc_path] } if {$browser eq {}} { From 871f4c97ad7e021d1a0a98c80c5da77fcf70e4af Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 18 Feb 2007 21:06:48 -0500 Subject: [PATCH 033/201] git-gui: Display all authors of git-gui. Now that git-gui has been released to the public as part of Git 1.5.0 I am starting to see some work from other people beyond myself and Paul. Consequently the copyright for git-gui is not strictly the two of us anymore, and these others deserve to have some credit given to them. Signed-off-by: Shawn O. Pearce --- .gitignore | 1 + CREDITS-GEN | 58 ++++++++++++++++++++++++++++++++++++++++++++++++ Makefile | 23 +++++++++++-------- git-gui.sh | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 136 insertions(+), 10 deletions(-) create mode 100755 CREDITS-GEN diff --git a/.gitignore b/.gitignore index c714d382e8..805ca2e1c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +CREDITS-FILE GIT-VERSION-FILE git-citool git-gui diff --git a/CREDITS-GEN b/CREDITS-GEN new file mode 100755 index 0000000000..da2c07629e --- /dev/null +++ b/CREDITS-GEN @@ -0,0 +1,58 @@ +#!/bin/sh + +CF=CREDITS-FILE +tip= + +tree_search () +{ + head=$1 + tree=$2 + for p in $(git rev-list --parents --max-count=1 $head 2>/dev/null) + do + test $tree = $(git rev-parse $p^{tree} 2>/dev/null) && + vn=$(git describe --abbrev=4 $p 2>/dev/null) && + case "$vn" in + gitgui-[0-9]*) echo $p; break;; + esac + done +} + +generate_credits () +{ + tip=$1 && + rm -f $CF && + git shortlog -n -s $tip | sed 's/: .*$//' >$CF || exit +} + +# Always use the tarball credits file if found, just +# in case we are somehow contained in a larger git +# repository that doesn't actually track our state. +# (At least one package manager is doing this.) +# +# We may be a subproject, so try looking for the merge +# commit that supplied this directory content if we are +# not at the toplevel. We probably will always be the +# second parent in the commit, but we shouldn't rely on +# that fact. +# + +if test -f credits +then + rm -f $CF && + cp credits $CF || exit +elif prefix="$(git rev-parse --show-prefix 2>/dev/null)" && + test -n "$prefix" && + head=$(git rev-list --max-count=1 HEAD -- . 2>/dev/null) && + tree=$(git rev-parse --verify "HEAD:$prefix" 2>/dev/null) && + tip=$(tree_search $head $tree) && + test -n "$tip" +then + generate_credits $tip || exit +elif tip="$(git rev-parse --verify HEAD 2>/dev/null)" && + test -n "$tip" +then + generate_credits $tip || exit +else + echo "error: Cannot locate authorship information." >&2 + exit 1 +fi diff --git a/Makefile b/Makefile index fd82d9d16d..66538ba1ad 100644 --- a/Makefile +++ b/Makefile @@ -4,9 +4,8 @@ GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE @$(SHELL_PATH) ./GIT-VERSION-GEN -include GIT-VERSION-FILE -SCRIPT_SH = git-gui.sh GITGUI_BUILT_INS = git-citool -ALL_PROGRAMS = $(GITGUI_BUILT_INS) $(patsubst %.sh,%,$(SCRIPT_SH)) +ALL_PROGRAMS = git-gui $(GITGUI_BUILT_INS) ifndef SHELL_PATH SHELL_PATH = /bin/sh @@ -24,20 +23,24 @@ DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) gitexecdir_SQ = $(subst ','\'',$(gitexecdir)) SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) -$(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh +git-gui: git-gui.sh GIT-VERSION-FILE CREDITS-FILE rm -f $@ $@+ - sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ + sed -n \ + -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's/@@GITGUI_VERSION@@/$(GITGUI_VERSION)/g' \ + -e '1,/^set gitgui_credits /p' \ $@.sh >$@+ + cat CREDITS-FILE >>$@+ + sed -e '1,/^set gitgui_credits /d' $@.sh >>$@+ chmod +x $@+ mv $@+ $@ +CREDITS-FILE: CREDITS-GEN .FORCE-CREDITS-FILE + $(SHELL_PATH) ./CREDITS-GEN + $(GITGUI_BUILT_INS): git-gui rm -f $@ && ln git-gui $@ -# These can record GITGUI_VERSION -$(patsubst %.sh,%,$(SCRIPT_SH)): GIT-VERSION-FILE - all:: $(ALL_PROGRAMS) install: all @@ -45,12 +48,14 @@ install: all $(INSTALL) git-gui '$(DESTDIR_SQ)$(gitexecdir_SQ)' $(foreach p,$(GITGUI_BUILT_INS), rm -f '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' && ln '$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' ;) -dist-version: +dist-version: CREDITS-FILE @mkdir -p $(TARDIR) @echo $(GITGUI_VERSION) > $(TARDIR)/version + @cat CREDITS-FILE > $(TARDIR)/credits clean:: - rm -f $(ALL_PROGRAMS) GIT-VERSION-FILE + rm -f $(ALL_PROGRAMS) GIT-VERSION-FILE CREDITS-FILE .PHONY: all install dist-version clean .PHONY: .FORCE-GIT-VERSION-FILE +.PHONY: .FORCE-CREDITS-FILE diff --git a/git-gui.sh b/git-gui.sh index 63848dc705..cd3afedc02 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -4,7 +4,7 @@ exec wish "$0" -- "$@" set appvers {@@GITGUI_VERSION@@} set copyright { -Copyright 2006, 2007 Shawn Pearce, Paul Mackerras. +Copyright 2006, 2007 Shawn Pearce, et. al. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,6 +19,9 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA} +set gitgui_credits { +Paul Mackerras +} ###################################################################### ## @@ -4477,6 +4480,61 @@ proc do_commit {} { commit_tree } +proc do_credits {} { + global gitgui_credits + + set w .credits_dialog + + toplevel $w + wm geometry $w "+[winfo rootx .]+[winfo rooty .]" + + label $w.header -text {git-gui Contributors} -font font_uibold + pack $w.header -side top -fill x + + frame $w.buttons + button $w.buttons.close -text {Close} \ + -font font_ui \ + -command [list destroy $w] + pack $w.buttons.close -side right + pack $w.buttons -side bottom -fill x -pady 10 -padx 10 + + frame $w.credits + text $w.credits.t \ + -background [$w.header cget -background] \ + -yscrollcommand [list $w.credits.sby set] \ + -width 20 \ + -height 10 \ + -wrap none \ + -borderwidth 1 \ + -relief solid \ + -padx 5 -pady 5 \ + -font font_ui + scrollbar $w.credits.sby -command [list $w.credits.t yview] + pack $w.credits.sby -side right -fill y + pack $w.credits.t -fill both -expand 1 + pack $w.credits -side top -fill both -expand 1 -padx 5 -pady 5 + + label $w.desc \ + -text "All portions are copyrighted by their respective authors +and are distributed under the GNU General Public License." \ + -padx 5 -pady 5 \ + -justify left \ + -anchor w \ + -borderwidth 1 \ + -relief solid \ + -font font_ui + pack $w.desc -side top -fill x -padx 5 -pady 5 + + $w.credits.t insert end "[string trim $gitgui_credits]\n" + $w.credits.t conf -state disabled + $w.credits.t see 1.0 + + bind $w "grab $w; focus $w" + bind $w [list destroy $w] + wm title $w [$w.header cget -text] + tkwait window $w +} + proc do_about {} { global appvers copyright global tcl_patchLevel tk_patchLevel @@ -4493,6 +4551,10 @@ proc do_about {} { button $w.buttons.close -text {Close} \ -font font_ui \ -command [list destroy $w] + button $w.buttons.credits -text {Contributors} \ + -font font_ui \ + -command do_credits + pack $w.buttons.credits -side left pack $w.buttons.close -side right pack $w.buttons -side bottom -fill x -pady 10 -padx 10 From 019f42a4ffe66e35cfe32a18c3def0183e6a0129 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 18 Feb 2007 21:08:04 -0500 Subject: [PATCH 034/201] git-gui: Change summary of git-gui. Since git-gui does more than create commits, it is unfair to call it "a commit creation tool". Instead lets just call it a graphical user interface. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-gui.sh b/git-gui.sh index cd3afedc02..405813069b 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -4559,7 +4559,7 @@ proc do_about {} { pack $w.buttons -side bottom -fill x -pady 10 -padx 10 label $w.desc \ - -text "git-gui - a commit creation tool for Git. + -text "git-gui - a graphical user interface for Git. $copyright" \ -padx 5 -pady 5 \ -justify left \ From cff0302c14522ba7b34265ee39f23e6321a4777d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 20 Feb 2007 01:51:22 -0800 Subject: [PATCH 035/201] Add prefixcmp() We have too many strncmp(a, b, strlen(b)). Signed-off-by: Junio C Hamano --- git-compat-util.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/git-compat-util.h b/git-compat-util.h index 9863cf671f..d027c36b97 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -279,4 +279,9 @@ static inline int sane_case(int x, int high) return x; } +static inline int prefixcmp(const char *str, const char *prefix) +{ + return strncmp(str, prefix, strlen(prefix)); +} + #endif From cc44c7655fe2dd0cfb46e841156634fe622df397 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 20 Feb 2007 01:53:29 -0800 Subject: [PATCH 036/201] Mechanical conversion to use prefixcmp() This mechanically converts strncmp() to use prefixcmp(), but only when the parameters match specific patterns, so that they can be verified easily. Leftover from this will be fixed in a separate step, including idiotic conversions like if (!strncmp("foo", arg, 3)) => if (!(-prefixcmp(arg, "foo"))) This was done by using this script in px.perl #!/usr/bin/perl -i.bak -p if (/strncmp\(([^,]+), "([^\\"]*)", (\d+)\)/ && (length($2) == $3)) { s|strncmp\(([^,]+), "([^\\"]*)", (\d+)\)|prefixcmp($1, "$2")|; } if (/strncmp\("([^\\"]*)", ([^,]+), (\d+)\)/ && (length($1) == $3)) { s|strncmp\("([^\\"]*)", ([^,]+), (\d+)\)|(-prefixcmp($2, "$1"))|; } and running: $ git grep -l strncmp -- '*.c' | xargs perl px.perl Signed-off-by: Junio C Hamano --- builtin-apply.c | 12 ++++++------ builtin-archive.c | 10 +++++----- builtin-blame.c | 6 +++--- builtin-branch.c | 12 ++++++------ builtin-checkout-index.c | 4 ++-- builtin-describe.c | 6 +++--- builtin-fmt-merge-msg.c | 10 +++++----- builtin-for-each-ref.c | 6 +++--- builtin-fsck.c | 2 +- builtin-grep.c | 6 +++--- builtin-init-db.c | 4 ++-- builtin-log.c | 12 ++++++------ builtin-ls-files.c | 8 ++++---- builtin-mailinfo.c | 2 +- builtin-name-rev.c | 8 ++++---- builtin-pack-objects.c | 6 +++--- builtin-pack-refs.c | 2 +- builtin-push.c | 18 ++++++++--------- builtin-read-tree.c | 4 ++-- builtin-reflog.c | 4 ++-- builtin-rerere.c | 6 +++--- builtin-rev-parse.c | 10 +++++----- builtin-shortlog.c | 6 +++--- builtin-show-branch.c | 20 +++++++++---------- builtin-show-ref.c | 12 ++++++------ builtin-tar-tree.c | 2 +- builtin-unpack-objects.c | 2 +- builtin-write-tree.c | 2 +- connect.c | 6 +++--- daemon.c | 32 +++++++++++++++--------------- diff.c | 30 ++++++++++++++-------------- exec_cmd.c | 2 +- fast-import.c | 42 ++++++++++++++++++++-------------------- fetch-pack.c | 12 ++++++------ git.c | 10 +++++----- help.c | 4 ++-- http-fetch.c | 2 +- http-push.c | 8 ++++---- index-pack.c | 4 ++-- peek-remote.c | 4 ++-- receive-pack.c | 4 ++-- refs.c | 8 ++++---- revision.c | 30 ++++++++++++++-------------- send-pack.c | 4 ++-- setup.c | 2 +- shell.c | 2 +- upload-pack.c | 10 +++++----- wt-status.c | 4 ++-- 48 files changed, 211 insertions(+), 211 deletions(-) diff --git a/builtin-apply.c b/builtin-apply.c index abe3538715..d67817834b 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -1129,11 +1129,11 @@ static struct fragment *parse_binary_hunk(char **buf_p, *status_p = 0; - if (!strncmp(buffer, "delta ", 6)) { + if (!prefixcmp(buffer, "delta ")) { patch_method = BINARY_DELTA_DEFLATED; origlen = strtoul(buffer + 6, NULL, 10); } - else if (!strncmp(buffer, "literal ", 8)) { + else if (!prefixcmp(buffer, "literal ")) { patch_method = BINARY_LITERAL_DEFLATED; origlen = strtoul(buffer + 8, NULL, 10); } @@ -2608,14 +2608,14 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) read_stdin = 0; continue; } - if (!strncmp(arg, "--exclude=", 10)) { + if (!prefixcmp(arg, "--exclude=")) { struct excludes *x = xmalloc(sizeof(*x)); x->path = arg + 10; x->next = excludes; excludes = x; continue; } - if (!strncmp(arg, "-p", 2)) { + if (!prefixcmp(arg, "-p")) { p_value = atoi(arg + 2); continue; } @@ -2669,13 +2669,13 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) line_termination = 0; continue; } - if (!strncmp(arg, "-C", 2)) { + if (!prefixcmp(arg, "-C")) { p_context = strtoul(arg + 2, &end, 0); if (*end != '\0') die("unrecognized context count '%s'", arg + 2); continue; } - if (!strncmp(arg, "--whitespace=", 13)) { + if (!prefixcmp(arg, "--whitespace=")) { whitespace_option = arg + 13; parse_whitespace_option(arg + 13); continue; diff --git a/builtin-archive.c b/builtin-archive.c index f613ac2516..0c56de0698 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -35,7 +35,7 @@ static int run_remote_archiver(const char *remote, int argc, for (i = 1; i < argc; i++) { const char *arg = argv[i]; - if (!strncmp("--exec=", arg, 7)) { + if (!(-prefixcmp(arg, "--exec="))) { if (exec_at) die("multiple --exec specified"); exec = arg + 7; @@ -62,7 +62,7 @@ static int run_remote_archiver(const char *remote, int argc, if (buf[len-1] == '\n') buf[--len] = 0; if (strcmp(buf, "ACK")) { - if (len > 5 && !strncmp(buf, "NACK ", 5)) + if (len > 5 && !prefixcmp(buf, "NACK ")) die("git-archive: NACK %s", buf + 5); die("git-archive: protocol error"); } @@ -166,11 +166,11 @@ int parse_archive_args(int argc, const char **argv, struct archiver *ar) verbose = 1; continue; } - if (!strncmp(arg, "--format=", 9)) { + if (!prefixcmp(arg, "--format=")) { format = arg + 9; continue; } - if (!strncmp(arg, "--prefix=", 9)) { + if (!prefixcmp(arg, "--prefix=")) { base = arg + 9; continue; } @@ -218,7 +218,7 @@ static const char *extract_remote_arg(int *ac, const char **av) if (!strcmp(arg, "--")) no_more_options = 1; if (!no_more_options) { - if (!strncmp(arg, "--remote=", 9)) { + if (!prefixcmp(arg, "--remote=")) { if (remote) die("Multiple --remote specified"); remote = arg + 9; diff --git a/builtin-blame.c b/builtin-blame.c index 5669a169ff..db311bfba9 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -2097,17 +2097,17 @@ int cmd_blame(int argc, const char **argv, const char *prefix) output_option |= OUTPUT_LONG_OBJECT_NAME; else if (!strcmp("-S", arg) && ++i < argc) revs_file = argv[i]; - else if (!strncmp("-M", arg, 2)) { + else if (!(-prefixcmp(arg, "-M"))) { opt |= PICKAXE_BLAME_MOVE; blame_move_score = parse_score(arg+2); } - else if (!strncmp("-C", arg, 2)) { + else if (!(-prefixcmp(arg, "-C"))) { if (opt & PICKAXE_BLAME_COPY) opt |= PICKAXE_BLAME_COPY_HARDER; opt |= PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE; blame_copy_score = parse_score(arg+2); } - else if (!strncmp("-L", arg, 2)) { + else if (!(-prefixcmp(arg, "-L"))) { if (!arg[2]) { if (++i >= argc) usage(blame_usage); diff --git a/builtin-branch.c b/builtin-branch.c index d0e7209368..d0179b00a2 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -59,7 +59,7 @@ int git_branch_config(const char *var, const char *value) branch_use_color = git_config_colorbool(var, value); return 0; } - if (!strncmp(var, "color.branch.", 13)) { + if (!prefixcmp(var, "color.branch.")) { int slot = parse_branch_color_slot(var, 13); color_parse(value, var, branch_colors[slot]); return 0; @@ -178,13 +178,13 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, int len; /* Detect kind */ - if (!strncmp(refname, "refs/heads/", 11)) { + if (!prefixcmp(refname, "refs/heads/")) { kind = REF_LOCAL_BRANCH; refname += 11; - } else if (!strncmp(refname, "refs/remotes/", 13)) { + } else if (!prefixcmp(refname, "refs/remotes/")) { kind = REF_REMOTE_BRANCH; refname += 13; - } else if (!strncmp(refname, "refs/tags/", 10)) { + } else if (!prefixcmp(refname, "refs/tags/")) { kind = REF_TAG; refname += 10; } @@ -446,7 +446,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) reflog = 1; continue; } - if (!strncmp(arg, "--abbrev=", 9)) { + if (!prefixcmp(arg, "--abbrev=")) { abbrev = atoi(arg+9); continue; } @@ -476,7 +476,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) detached = 1; } else { - if (strncmp(head, "refs/heads/", 11)) + if (prefixcmp(head, "refs/heads/")) die("HEAD not found below refs/heads!"); head += 11; } diff --git a/builtin-checkout-index.c b/builtin-checkout-index.c index b097c888a0..afe4b0e452 100644 --- a/builtin-checkout-index.c +++ b/builtin-checkout-index.c @@ -223,12 +223,12 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) to_tempfile = 1; continue; } - if (!strncmp(arg, "--prefix=", 9)) { + if (!prefixcmp(arg, "--prefix=")) { state.base_dir = arg+9; state.base_dir_len = strlen(state.base_dir); continue; } - if (!strncmp(arg, "--stage=", 8)) { + if (!prefixcmp(arg, "--stage=")) { if (!strcmp(arg + 8, "all")) { to_tempfile = 1; checkout_stage = CHECKOUT_ALL; diff --git a/builtin-describe.c b/builtin-describe.c index bcc645622a..165917e40d 100644 --- a/builtin-describe.c +++ b/builtin-describe.c @@ -52,7 +52,7 @@ static int get_name(const char *path, const unsigned char *sha1, int flag, void * If --tags, then any tags are used. * Otherwise only annotated tags are used. */ - if (!strncmp(path, "refs/tags/", 10)) { + if (!prefixcmp(path, "refs/tags/")) { if (object->type == OBJ_TAG) prio = 2; else @@ -254,12 +254,12 @@ int cmd_describe(int argc, const char **argv, const char *prefix) all = 1; else if (!strcmp(arg, "--tags")) tags = 1; - else if (!strncmp(arg, "--abbrev=", 9)) { + else if (!prefixcmp(arg, "--abbrev=")) { abbrev = strtoul(arg + 9, NULL, 10); if (abbrev != 0 && (abbrev < MINIMUM_ABBREV || 40 < abbrev)) abbrev = DEFAULT_ABBREV; } - else if (!strncmp(arg, "--candidates=", 13)) { + else if (!prefixcmp(arg, "--candidates=")) { max_candidates = strtoul(arg + 13, NULL, 10); if (max_candidates < 1) max_candidates = 1; diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c index 87d3d63ec7..1489883564 100644 --- a/builtin-fmt-merge-msg.c +++ b/builtin-fmt-merge-msg.c @@ -81,7 +81,7 @@ static int handle_line(char *line) if (len < 43 || line[40] != '\t') return 1; - if (!strncmp(line + 41, "not-for-merge", 13)) + if (!prefixcmp(line + 41, "not-for-merge")) return 0; if (line[41] != '\t') @@ -119,15 +119,15 @@ static int handle_line(char *line) if (pulling_head) { origin = xstrdup(src); src_data->head_status |= 1; - } else if (!strncmp(line, "branch ", 7)) { + } else if (!prefixcmp(line, "branch ")) { origin = xstrdup(line + 7); append_to_list(&src_data->branch, origin, NULL); src_data->head_status |= 2; - } else if (!strncmp(line, "tag ", 4)) { + } else if (!prefixcmp(line, "tag ")) { origin = line; append_to_list(&src_data->tag, xstrdup(origin + 4), NULL); src_data->head_status |= 2; - } else if (!strncmp(line, "remote branch ", 14)) { + } else if (!prefixcmp(line, "remote branch ")) { origin = xstrdup(line + 14); append_to_list(&src_data->r_branch, origin, NULL); src_data->head_status |= 2; @@ -280,7 +280,7 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) current_branch = resolve_ref("HEAD", head_sha1, 1, NULL); if (!current_branch) die("No current branch"); - if (!strncmp(current_branch, "refs/heads/", 11)) + if (!prefixcmp(current_branch, "refs/heads/")) current_branch += 11; while (fgets(line, sizeof(line), in)) { diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c index 16c785f047..ac0b9f6088 100644 --- a/builtin-for-each-ref.c +++ b/builtin-for-each-ref.c @@ -814,7 +814,7 @@ int cmd_for_each_ref(int ac, const char **av, char *prefix) i++; break; } - if (!strncmp(arg, "--format=", 9)) { + if (!prefixcmp(arg, "--format=")) { if (format) die("more than one --format?"); format = arg + 9; @@ -844,7 +844,7 @@ int cmd_for_each_ref(int ac, const char **av, char *prefix) quote_style = QUOTE_TCL; continue; } - if (!strncmp(arg, "--count=", 8)) { + if (!prefixcmp(arg, "--count=")) { if (maxcount) die("more than one --count?"); maxcount = atoi(arg + 8); @@ -852,7 +852,7 @@ int cmd_for_each_ref(int ac, const char **av, char *prefix) die("The number %s did not parse", arg); continue; } - if (!strncmp(arg, "--sort=", 7)) { + if (!prefixcmp(arg, "--sort=")) { struct ref_sort *s = xcalloc(1, sizeof(*s)); int len; diff --git a/builtin-fsck.c b/builtin-fsck.c index 6da3814d59..6abf498d2b 100644 --- a/builtin-fsck.c +++ b/builtin-fsck.c @@ -546,7 +546,7 @@ static int fsck_head_link(void) if (!head_points_at || !(flag & REF_ISSYMREF)) return error("HEAD is not a symbolic ref"); - if (strncmp(head_points_at, "refs/heads/", 11)) + if (prefixcmp(head_points_at, "refs/heads/")) return error("HEAD points to something strange (%s)", head_points_at); if (is_null_sha1(sha1)) diff --git a/builtin-grep.c b/builtin-grep.c index 2bfbdb7140..cec22047c9 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -527,9 +527,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) opt.word_regexp = 1; continue; } - if (!strncmp("-A", arg, 2) || - !strncmp("-B", arg, 2) || - !strncmp("-C", arg, 2) || + if (!(-prefixcmp(arg, "-A")) || + !(-prefixcmp(arg, "-B")) || + !(-prefixcmp(arg, "-C")) || (arg[0] == '-' && '1' <= arg[1] && arg[1] <= '9')) { unsigned num; const char *scan; diff --git a/builtin-init-db.c b/builtin-init-db.c index 12e43d0db4..4df9fd0fad 100644 --- a/builtin-init-db.c +++ b/builtin-init-db.c @@ -283,11 +283,11 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) for (i = 1; i < argc; i++, argv++) { const char *arg = argv[1]; - if (!strncmp(arg, "--template=", 11)) + if (!prefixcmp(arg, "--template=")) template_dir = arg+11; else if (!strcmp(arg, "--shared")) shared_repository = PERM_GROUP; - else if (!strncmp(arg, "--shared=", 9)) + else if (!prefixcmp(arg, "--shared=")) shared_repository = git_config_perm("arg", arg+9); else usage(init_db_usage); diff --git a/builtin-log.c b/builtin-log.c index af2de54371..ad1e8c054d 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -32,7 +32,7 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, rev->always_show_header = 0; for (i = 1; i < argc; i++) { const char *arg = argv[i]; - if (!strncmp(arg, "--encoding=", 11)) { + if (!prefixcmp(arg, "--encoding=")) { arg += 11; if (strcmp(arg, "none")) git_log_output_encoding = strdup(arg); @@ -287,7 +287,7 @@ static void reopen_stdout(struct commit *commit, int nr, int keep_subject) sol += 2; /* strip [PATCH] or [PATCH blabla] */ - if (!keep_subject && !strncmp(sol, "[PATCH", 6)) { + if (!keep_subject && !prefixcmp(sol, "[PATCH")) { char *eos = strchr(sol + 6, ']'); if (eos) { while (isspace(*eos)) @@ -435,7 +435,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) else if (!strcmp(argv[i], "-n") || !strcmp(argv[i], "--numbered")) numbered = 1; - else if (!strncmp(argv[i], "--start-number=", 15)) + else if (!prefixcmp(argv[i], "--start-number=")) start_number = strtol(argv[i] + 15, NULL, 10); else if (!strcmp(argv[i], "--start-number")) { i++; @@ -471,13 +471,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) } else if (!strcmp(argv[i], "--attach")) rev.mime_boundary = git_version_string; - else if (!strncmp(argv[i], "--attach=", 9)) + else if (!prefixcmp(argv[i], "--attach=")) rev.mime_boundary = argv[i] + 9; else if (!strcmp(argv[i], "--ignore-if-in-upstream")) ignore_if_in_upstream = 1; else if (!strcmp(argv[i], "--thread")) thread = 1; - else if (!strncmp(argv[i], "--in-reply-to=", 14)) + else if (!prefixcmp(argv[i], "--in-reply-to=")) in_reply_to = argv[i] + 14; else if (!strcmp(argv[i], "--in-reply-to")) { i++; @@ -485,7 +485,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) die("Need a Message-Id for --in-reply-to"); in_reply_to = argv[i]; } - else if (!strncmp(argv[i], "--suffix=", 9)) + else if (!prefixcmp(argv[i], "--suffix=")) fmt_patch_suffix = argv[i] + 9; else argv[j++] = argv[i]; diff --git a/builtin-ls-files.c b/builtin-ls-files.c index ac89eb2f77..4e1d5af634 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -406,7 +406,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) add_exclude(argv[++i], "", 0, &dir.exclude_list[EXC_CMDL]); continue; } - if (!strncmp(arg, "--exclude=", 10)) { + if (!prefixcmp(arg, "--exclude=")) { exc_given = 1; add_exclude(arg+10, "", 0, &dir.exclude_list[EXC_CMDL]); continue; @@ -416,12 +416,12 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) add_excludes_from_file(&dir, argv[++i]); continue; } - if (!strncmp(arg, "--exclude-from=", 15)) { + if (!prefixcmp(arg, "--exclude-from=")) { exc_given = 1; add_excludes_from_file(&dir, arg+15); continue; } - if (!strncmp(arg, "--exclude-per-directory=", 24)) { + if (!prefixcmp(arg, "--exclude-per-directory=")) { exc_given = 1; dir.exclude_per_dir = arg + 24; continue; @@ -434,7 +434,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) error_unmatch = 1; continue; } - if (!strncmp(arg, "--abbrev=", 9)) { + if (!prefixcmp(arg, "--abbrev=")) { abbrev = strtoul(arg+9, NULL, 10); if (abbrev && abbrev < MINIMUM_ABBREV) abbrev = MINIMUM_ABBREV; diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index 583da38b67..6ee6b0b26c 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -811,7 +811,7 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix) metainfo_charset = def_charset; else if (!strcmp(argv[1], "-n")) metainfo_charset = NULL; - else if (!strncmp(argv[1], "--encoding=", 11)) + else if (!prefixcmp(argv[1], "--encoding=")) metainfo_charset = argv[1] + 11; else usage(mailinfo_usage); diff --git a/builtin-name-rev.c b/builtin-name-rev.c index 36f1ba6d38..2c3d14c1ba 100644 --- a/builtin-name-rev.c +++ b/builtin-name-rev.c @@ -85,7 +85,7 @@ static int name_ref(const char *path, const unsigned char *sha1, int flags, void struct name_ref_data *data = cb_data; int deref = 0; - if (data->tags_only && strncmp(path, "refs/tags/", 10)) + if (data->tags_only && prefixcmp(path, "refs/tags/")) return 0; if (data->ref_filter && fnmatch(data->ref_filter, path, 0)) @@ -101,9 +101,9 @@ static int name_ref(const char *path, const unsigned char *sha1, int flags, void if (o && o->type == OBJ_COMMIT) { struct commit *commit = (struct commit *)o; - if (!strncmp(path, "refs/heads/", 11)) + if (!prefixcmp(path, "refs/heads/")) path = path + 11; - else if (!strncmp(path, "refs/", 5)) + else if (!prefixcmp(path, "refs/")) path = path + 5; name_rev(commit, xstrdup(path), 0, 0, deref); @@ -156,7 +156,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) } else if (!strcmp(*argv, "--tags")) { data.tags_only = 1; continue; - } else if (!strncmp(*argv, "--refs=", 7)) { + } else if (!prefixcmp(*argv, "--refs=")) { data.ref_filter = *argv + 7; continue; } else if (!strcmp(*argv, "--all")) { diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 3824ee33ac..71113d8fb3 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -1579,14 +1579,14 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) incremental = 1; continue; } - if (!strncmp("--window=", arg, 9)) { + if (!(-prefixcmp(arg, "--window="))) { char *end; window = strtoul(arg+9, &end, 0); if (!arg[9] || *end) usage(pack_usage); continue; } - if (!strncmp("--depth=", arg, 8)) { + if (!(-prefixcmp(arg, "--depth="))) { char *end; depth = strtoul(arg+8, &end, 0); if (!arg[8] || *end) @@ -1622,7 +1622,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) continue; } if (!strcmp("--unpacked", arg) || - !strncmp("--unpacked=", arg, 11) || + !(-prefixcmp(arg, "--unpacked=")) || !strcmp("--reflog", arg) || !strcmp("--all", arg)) { use_internal_rev_list = 1; diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c index 3de9b3eefd..d080e30d67 100644 --- a/builtin-pack-refs.c +++ b/builtin-pack-refs.c @@ -36,7 +36,7 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, /* Do not pack the symbolic refs */ if ((flags & REF_ISSYMREF)) return 0; - is_tag_ref = !strncmp(path, "refs/tags/", 10); + is_tag_ref = !prefixcmp(path, "refs/tags/"); /* ALWAYS pack refs that were already packed or are tags */ if (!cb->all && !is_tag_ref && !(flags & REF_ISPACKED)) diff --git a/builtin-push.c b/builtin-push.c index c45649e26c..2b98ba3231 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -32,7 +32,7 @@ static int expand_one_ref(const char *ref, const unsigned char *sha1, int flag, /* Ignore the "refs/" at the beginning of the refname */ ref += 5; - if (!strncmp(ref, "tags/", 5)) + if (!prefixcmp(ref, "tags/")) add_refspec(xstrdup(ref)); return 0; } @@ -149,10 +149,10 @@ static int get_remotes_uri(const char *repo, const char *uri[MAX_URI]) int is_refspec; char *s, *p; - if (!strncmp("URL:", buffer, 4)) { + if (!(-prefixcmp(buffer, "URL:"))) { is_refspec = 0; s = buffer + 4; - } else if (!strncmp("Push:", buffer, 5)) { + } else if (!(-prefixcmp(buffer, "Push:"))) { is_refspec = 1; s = buffer + 5; } else @@ -195,7 +195,7 @@ static int config_get_receivepack; static int get_remote_config(const char* key, const char* value) { - if (!strncmp(key, "remote.", 7) && + if (!prefixcmp(key, "remote.") && !strncmp(key + 7, config_repo, config_repo_len)) { if (!strcmp(key + 7 + config_repo_len, ".url")) { if (config_current_uri < MAX_URI) @@ -324,8 +324,8 @@ static int do_push(const char *repo) const char **dest_refspec = refspec; const char *dest = uri[i]; const char *sender = "git-send-pack"; - if (!strncmp(dest, "http://", 7) || - !strncmp(dest, "https://", 8)) + if (!prefixcmp(dest, "http://") || + !prefixcmp(dest, "https://")) sender = "git-http-push"; else if (thin) argv[dest_argc++] = "--thin"; @@ -373,7 +373,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) verbose=1; continue; } - if (!strncmp(arg, "--repo=", 7)) { + if (!prefixcmp(arg, "--repo=")) { repo = arg+7; continue; } @@ -397,11 +397,11 @@ int cmd_push(int argc, const char **argv, const char *prefix) thin = 0; continue; } - if (!strncmp(arg, "--receive-pack=", 15)) { + if (!prefixcmp(arg, "--receive-pack=")) { receivepack = arg; continue; } - if (!strncmp(arg, "--exec=", 7)) { + if (!prefixcmp(arg, "--exec=")) { receivepack = arg; continue; } diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 8ba436dbac..e47715538b 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -133,7 +133,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) * entries and put the entries from the tree under the * given subdirectory. */ - if (!strncmp(arg, "--prefix=", 9)) { + if (!prefixcmp(arg, "--prefix=")) { if (stage || opts.merge || opts.prefix) usage(read_tree_usage); opts.prefix = arg + 9; @@ -179,7 +179,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) continue; } - if (!strncmp(arg, "--exclude-per-directory=", 24)) { + if (!prefixcmp(arg, "--exclude-per-directory=")) { struct dir_struct *dir; if (opts.dir) diff --git a/builtin-reflog.c b/builtin-reflog.c index 341555139e..cefb40da81 100644 --- a/builtin-reflog.c +++ b/builtin-reflog.c @@ -321,9 +321,9 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) const char *arg = argv[i]; if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n")) cb.dry_run = 1; - else if (!strncmp(arg, "--expire=", 9)) + else if (!prefixcmp(arg, "--expire=")) cb.expire_total = approxidate(arg + 9); - else if (!strncmp(arg, "--expire-unreachable=", 21)) + else if (!prefixcmp(arg, "--expire-unreachable=")) cb.expire_unreachable = approxidate(arg + 21); else if (!strcmp(arg, "--stale-fix")) cb.stalefix = 1; diff --git a/builtin-rerere.c b/builtin-rerere.c index 318d959d89..978105b497 100644 --- a/builtin-rerere.c +++ b/builtin-rerere.c @@ -105,11 +105,11 @@ static int handle_file(const char *path, SHA1_Init(&ctx); while (fgets(buf, sizeof(buf), f)) { - if (!strncmp("<<<<<<< ", buf, 8)) + if (!(-prefixcmp(buf, "<<<<<<< "))) hunk = 1; - else if (!strncmp("=======", buf, 7)) + else if (!(-prefixcmp(buf, "======="))) hunk = 2; - else if (!strncmp(">>>>>>> ", buf, 8)) { + else if (!(-prefixcmp(buf, ">>>>>>> "))) { hunk_no++; hunk = 0; if (memcmp(one->ptr, two->ptr, one->nr < two->nr ? diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index d53deaa369..a1c3411121 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -274,7 +274,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "--short") || - !strncmp(arg, "--short=", 8)) { + !prefixcmp(arg, "--short=")) { filter &= ~(DO_FLAGS|DO_NOREV); verify = 1; abbrev = DEFAULT_ABBREV; @@ -352,19 +352,19 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) : "false"); continue; } - if (!strncmp(arg, "--since=", 8)) { + if (!prefixcmp(arg, "--since=")) { show_datestring("--max-age=", arg+8); continue; } - if (!strncmp(arg, "--after=", 8)) { + if (!prefixcmp(arg, "--after=")) { show_datestring("--max-age=", arg+8); continue; } - if (!strncmp(arg, "--before=", 9)) { + if (!prefixcmp(arg, "--before=")) { show_datestring("--min-age=", arg+9); continue; } - if (!strncmp(arg, "--until=", 8)) { + if (!prefixcmp(arg, "--until=")) { show_datestring("--min-age=", arg+8); continue; } diff --git a/builtin-shortlog.c b/builtin-shortlog.c index edb40429ec..2f71a2a6e2 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -124,7 +124,7 @@ static void insert_author_oneline(struct path_list *list, else free(buffer); - if (!strncmp(oneline, "[PATCH", 6)) { + if (!prefixcmp(oneline, "[PATCH")) { char *eob = strchr(oneline, ']'); if (eob) { @@ -179,7 +179,7 @@ static void read_from_stdin(struct path_list *list) while (fgets(buffer, sizeof(buffer), stdin) != NULL) { char *bob; if ((buffer[0] == 'A' || buffer[0] == 'a') && - !strncmp(buffer + 1, "uthor: ", 7) && + !prefixcmp(buffer + 1, "uthor: ") && (bob = strchr(buffer + 7, '<')) != NULL) { char buffer2[1024], offset = 0; @@ -230,7 +230,7 @@ static void get_from_rev(struct rev_info *rev, struct path_list *list) else eol++; - if (!strncmp(buffer, "author ", 7)) { + if (!prefixcmp(buffer, "author ")) { char *bracket = strchr(buffer, '<'); if (bracket == NULL || bracket > eol) diff --git a/builtin-show-branch.c b/builtin-show-branch.c index 0d94e40df8..bf6aee49f7 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -266,7 +266,7 @@ static void show_one_commit(struct commit *commit, int no_name) pretty, sizeof(pretty), 0, NULL, NULL, 0); else strcpy(pretty, "(unavailable)"); - if (!strncmp(pretty, "[PATCH] ", 8)) + if (!prefixcmp(pretty, "[PATCH] ")) cp = pretty + 8; else cp = pretty; @@ -404,7 +404,7 @@ static int append_remote_ref(const char *refname, const unsigned char *sha1, int static int append_tag_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { - if (strncmp(refname, "refs/tags/", 10)) + if (prefixcmp(refname, "refs/tags/")) return 0; return append_ref(refname + 5, sha1, 0); } @@ -435,9 +435,9 @@ static int append_matching_ref(const char *refname, const unsigned char *sha1, i return 0; if (fnmatch(match_ref_pattern, tail, 0)) return 0; - if (!strncmp("refs/heads/", refname, 11)) + if (!(-prefixcmp(refname, "refs/heads/"))) return append_head_ref(refname, sha1, flag, cb_data); - if (!strncmp("refs/tags/", refname, 10)) + if (!(-prefixcmp(refname, "refs/tags/"))) return append_tag_ref(refname, sha1, flag, cb_data); return append_ref(refname, sha1, 0); } @@ -462,11 +462,11 @@ static int rev_is_head(char *head, int headlen, char *name, if ((!head[0]) || (head_sha1 && sha1 && hashcmp(head_sha1, sha1))) return 0; - if (!strncmp(head, "refs/heads/", 11)) + if (!prefixcmp(head, "refs/heads/")) head += 11; - if (!strncmp(name, "refs/heads/", 11)) + if (!prefixcmp(name, "refs/heads/")) name += 11; - else if (!strncmp(name, "heads/", 6)) + else if (!prefixcmp(name, "heads/")) name += 6; return !strcmp(head, name); } @@ -635,7 +635,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) with_current_branch = 1; else if (!strcmp(arg, "--sha1-name")) sha1_name = 1; - else if (!strncmp(arg, "--more=", 7)) + else if (!prefixcmp(arg, "--more=")) extra = atoi(arg + 7); else if (!strcmp(arg, "--merge-base")) merge_base = 1; @@ -652,9 +652,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) else if (!strcmp(arg, "--reflog") || !strcmp(arg, "-g")) { reflog = DEFAULT_REFLOG; } - else if (!strncmp(arg, "--reflog=", 9)) + else if (!prefixcmp(arg, "--reflog=")) parse_reflog_param(arg + 9, &reflog, &reflog_base); - else if (!strncmp(arg, "-g=", 3)) + else if (!prefixcmp(arg, "-g=")) parse_reflog_param(arg + 3, &reflog, &reflog_base); else usage(show_branch_usage); diff --git a/builtin-show-ref.c b/builtin-show-ref.c index 853f13f6ae..ae0edddac1 100644 --- a/builtin-show-ref.c +++ b/builtin-show-ref.c @@ -28,8 +28,8 @@ static int show_ref(const char *refname, const unsigned char *sha1, int flag, vo if (tags_only || heads_only) { int match; - match = heads_only && !strncmp(refname, "refs/heads/", 11); - match |= tags_only && !strncmp(refname, "refs/tags/", 10); + match = heads_only && !prefixcmp(refname, "refs/heads/"); + match |= tags_only && !prefixcmp(refname, "refs/tags/"); if (!match) return 0; } @@ -178,8 +178,8 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix) hash_only = 1; continue; } - if (!strncmp(arg, "--hash=", 7) || - (!strncmp(arg, "--abbrev", 8) && + if (!prefixcmp(arg, "--hash=") || + (!prefixcmp(arg, "--abbrev") && (arg[8] == '=' || arg[8] == '\0'))) { if (arg[2] != 'h' && !arg[8]) /* --abbrev only */ @@ -215,7 +215,7 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix) } if (!strcmp(arg, "--exclude-existing")) return exclude_existing(NULL); - if (!strncmp(arg, "--exclude-existing=", 19)) + if (!prefixcmp(arg, "--exclude-existing=")) return exclude_existing(arg + 19); usage(show_ref_usage); } @@ -224,7 +224,7 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix) unsigned char sha1[20]; while (*pattern) { - if (!strncmp(*pattern, "refs/", 5) && + if (!prefixcmp(*pattern, "refs/") && resolve_ref(*pattern, sha1, 1, NULL)) { if (!quiet) show_one(*pattern, sha1); diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c index 8055ddab9b..28f8c1c164 100644 --- a/builtin-tar-tree.c +++ b/builtin-tar-tree.c @@ -31,7 +31,7 @@ int cmd_tar_tree(int argc, const char **argv, const char *prefix) nargv[nargc++] = "git-archive"; nargv[nargc++] = "--format=tar"; - if (2 <= argc && !strncmp("--remote=", argv[1], 9)) { + if (2 <= argc && !(-prefixcmp(argv[1], "--remote="))) { nargv[nargc++] = argv[1]; argv++; argc--; diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c index d351e02649..8f8e898516 100644 --- a/builtin-unpack-objects.c +++ b/builtin-unpack-objects.c @@ -369,7 +369,7 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix) recover = 1; continue; } - if (!strncmp(arg, "--pack_header=", 14)) { + if (!prefixcmp(arg, "--pack_header=")) { struct pack_header *hdr; char *c; diff --git a/builtin-write-tree.c b/builtin-write-tree.c index 50670dc7bf..90fc1cfcf4 100644 --- a/builtin-write-tree.c +++ b/builtin-write-tree.c @@ -70,7 +70,7 @@ int cmd_write_tree(int argc, const char **argv, const char *unused_prefix) const char *arg = argv[1]; if (!strcmp(arg, "--missing-ok")) missing_ok = 1; - else if (!strncmp(arg, "--prefix=", 9)) + else if (!prefixcmp(arg, "--prefix=")) prefix = arg + 9; else usage(write_tree_usage); diff --git a/connect.c b/connect.c index 78448889da..8a8a13bb72 100644 --- a/connect.c +++ b/connect.c @@ -96,7 +96,7 @@ int get_ack(int fd, unsigned char *result_sha1) line[--len] = 0; if (!strcmp(line, "NAK")) return 0; - if (!strncmp(line, "ACK ", 4)) { + if (!prefixcmp(line, "ACK ")) { if (!get_sha1_hex(line+4, result_sha1)) { if (strstr(line+45, "continue")) return 2; @@ -196,8 +196,8 @@ static int count_refspec_match(const char *pattern, */ if (namelen != patlen && patlen != namelen - 5 && - strncmp(name, "refs/heads/", 11) && - strncmp(name, "refs/tags/", 10)) { + prefixcmp(name, "refs/heads/") && + prefixcmp(name, "refs/tags/")) { /* We want to catch the case where only weak * matches are found and there are multiple * matches, and where more than one strong diff --git a/daemon.c b/daemon.c index 66f8d6f03d..cdbc23f63d 100644 --- a/daemon.c +++ b/daemon.c @@ -286,7 +286,7 @@ static int service_enabled; static int git_daemon_config(const char *var, const char *value) { - if (!strncmp(var, "daemon.", 7) && + if (!prefixcmp(var, "daemon.") && !strcmp(var + 7, service_looking_at->config_name)) { service_enabled = git_config_bool(var, value); return 0; @@ -562,7 +562,7 @@ static int execute(struct sockaddr *addr) for (i = 0; i < ARRAY_SIZE(daemon_service); i++) { struct daemon_service *s = &(daemon_service[i]); int namelen = strlen(s->name); - if (!strncmp("git-", line, 4) && + if (!(-prefixcmp(line, "git-")) && !strncmp(s->name, line + 4, namelen) && line[namelen + 4] == ' ') { /* @@ -1011,7 +1011,7 @@ int main(int argc, char **argv) for (i = 1; i < argc; i++) { char *arg = argv[i]; - if (!strncmp(arg, "--listen=", 9)) { + if (!prefixcmp(arg, "--listen=")) { char *p = arg + 9; char *ph = listen_addr = xmalloc(strlen(arg + 9) + 1); while (*p) @@ -1019,7 +1019,7 @@ int main(int argc, char **argv) *ph = 0; continue; } - if (!strncmp(arg, "--port=", 7)) { + if (!prefixcmp(arg, "--port=")) { char *end; unsigned long n; n = strtoul(arg+7, &end, 0); @@ -1045,11 +1045,11 @@ int main(int argc, char **argv) export_all_trees = 1; continue; } - if (!strncmp(arg, "--timeout=", 10)) { + if (!prefixcmp(arg, "--timeout=")) { timeout = atoi(arg+10); continue; } - if (!strncmp(arg, "--init-timeout=", 15)) { + if (!prefixcmp(arg, "--init-timeout=")) { init_timeout = atoi(arg+15); continue; } @@ -1057,11 +1057,11 @@ int main(int argc, char **argv) strict_paths = 1; continue; } - if (!strncmp(arg, "--base-path=", 12)) { + if (!prefixcmp(arg, "--base-path=")) { base_path = arg+12; continue; } - if (!strncmp(arg, "--interpolated-path=", 20)) { + if (!prefixcmp(arg, "--interpolated-path=")) { interpolated_path = arg+20; continue; } @@ -1073,11 +1073,11 @@ int main(int argc, char **argv) user_path = ""; continue; } - if (!strncmp(arg, "--user-path=", 12)) { + if (!prefixcmp(arg, "--user-path=")) { user_path = arg + 12; continue; } - if (!strncmp(arg, "--pid-file=", 11)) { + if (!prefixcmp(arg, "--pid-file=")) { pid_file = arg + 11; continue; } @@ -1086,27 +1086,27 @@ int main(int argc, char **argv) log_syslog = 1; continue; } - if (!strncmp(arg, "--user=", 7)) { + if (!prefixcmp(arg, "--user=")) { user_name = arg + 7; continue; } - if (!strncmp(arg, "--group=", 8)) { + if (!prefixcmp(arg, "--group=")) { group_name = arg + 8; continue; } - if (!strncmp(arg, "--enable=", 9)) { + if (!prefixcmp(arg, "--enable=")) { enable_service(arg + 9, 1); continue; } - if (!strncmp(arg, "--disable=", 10)) { + if (!prefixcmp(arg, "--disable=")) { enable_service(arg + 10, 0); continue; } - if (!strncmp(arg, "--allow-override=", 17)) { + if (!prefixcmp(arg, "--allow-override=")) { make_service_overridable(arg + 17, 1); continue; } - if (!strncmp(arg, "--forbid-override=", 18)) { + if (!prefixcmp(arg, "--forbid-override=")) { make_service_overridable(arg + 18, 0); continue; } diff --git a/diff.c b/diff.c index 07589c3b1e..fad13ab44b 100644 --- a/diff.c +++ b/diff.c @@ -77,7 +77,7 @@ int git_diff_ui_config(const char *var, const char *value) diff_detect_rename_default = DIFF_DETECT_RENAME; return 0; } - if (!strncmp(var, "diff.color.", 11) || !strncmp(var, "color.diff.", 11)) { + if (!prefixcmp(var, "diff.color.") || !strncmp(var, "color.diff.", 11)) { int slot = parse_diff_color_slot(var, 11); color_parse(value, var, diff_colors[slot]); return 0; @@ -1119,9 +1119,9 @@ static void builtin_diff(const char *name_a, xecfg.flags = XDL_EMIT_FUNCNAMES; if (!diffopts) ; - else if (!strncmp(diffopts, "--unified=", 10)) + else if (!prefixcmp(diffopts, "--unified=")) xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10); - else if (!strncmp(diffopts, "-u", 2)) + else if (!prefixcmp(diffopts, "-u")) xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10); ecb.outf = xdiff_outf; ecb.priv = &ecbdata; @@ -1957,7 +1957,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) else if (!strcmp(arg, "--shortstat")) { options->output_format |= DIFF_FORMAT_SHORTSTAT; } - else if (!strncmp(arg, "--stat", 6)) { + else if (!prefixcmp(arg, "--stat")) { char *end; int width = options->stat_width; int name_width = options->stat_name_width; @@ -1966,9 +1966,9 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) switch (*arg) { case '-': - if (!strncmp(arg, "-width=", 7)) + if (!prefixcmp(arg, "-width=")) width = strtoul(arg + 7, &end, 10); - else if (!strncmp(arg, "-name-width=", 12)) + else if (!prefixcmp(arg, "-name-width=")) name_width = strtoul(arg + 12, &end, 10); break; case '=': @@ -1993,7 +1993,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) } else if (!strcmp(arg, "-z")) options->line_termination = 0; - else if (!strncmp(arg, "-l", 2)) + else if (!prefixcmp(arg, "-l")) options->rename_limit = strtoul(arg+2, NULL, 10); else if (!strcmp(arg, "--full-index")) options->full_index = 1; @@ -2010,31 +2010,31 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->output_format |= DIFF_FORMAT_NAME_STATUS; else if (!strcmp(arg, "-R")) options->reverse_diff = 1; - else if (!strncmp(arg, "-S", 2)) + else if (!prefixcmp(arg, "-S")) options->pickaxe = arg + 2; else if (!strcmp(arg, "-s")) { options->output_format |= DIFF_FORMAT_NO_OUTPUT; } - else if (!strncmp(arg, "-O", 2)) + else if (!prefixcmp(arg, "-O")) options->orderfile = arg + 2; - else if (!strncmp(arg, "--diff-filter=", 14)) + else if (!prefixcmp(arg, "--diff-filter=")) options->filter = arg + 14; else if (!strcmp(arg, "--pickaxe-all")) options->pickaxe_opts = DIFF_PICKAXE_ALL; else if (!strcmp(arg, "--pickaxe-regex")) options->pickaxe_opts = DIFF_PICKAXE_REGEX; - else if (!strncmp(arg, "-B", 2)) { + else if (!prefixcmp(arg, "-B")) { if ((options->break_opt = diff_scoreopt_parse(arg)) == -1) return -1; } - else if (!strncmp(arg, "-M", 2)) { + else if (!prefixcmp(arg, "-M")) { if ((options->rename_score = diff_scoreopt_parse(arg)) == -1) return -1; options->detect_rename = DIFF_DETECT_RENAME; } - else if (!strncmp(arg, "-C", 2)) { + else if (!prefixcmp(arg, "-C")) { if ((options->rename_score = diff_scoreopt_parse(arg)) == -1) return -1; @@ -2044,7 +2044,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->find_copies_harder = 1; else if (!strcmp(arg, "--abbrev")) options->abbrev = DEFAULT_ABBREV; - else if (!strncmp(arg, "--abbrev=", 9)) { + else if (!prefixcmp(arg, "--abbrev=")) { options->abbrev = strtoul(arg + 9, NULL, 10); if (options->abbrev < MINIMUM_ABBREV) options->abbrev = MINIMUM_ABBREV; @@ -2553,7 +2553,7 @@ static void patch_id_consume(void *priv, char *line, unsigned long len) int new_len; /* Ignore line numbers when computing the SHA1 of the patch */ - if (!strncmp(line, "@@ -", 4)) + if (!prefixcmp(line, "@@ -")) return; new_len = remove_space(line, len); diff --git a/exec_cmd.c b/exec_cmd.c index 3996bce33f..9b74ed2f42 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -56,7 +56,7 @@ int execv_git_cmd(const char **argv) len = strlen(git_command); /* Trivial cleanup */ - while (!strncmp(exec_dir, "./", 2)) { + while (!prefixcmp(exec_dir, "./")) { exec_dir += 2; while (*exec_dir == '/') exec_dir++; diff --git a/fast-import.c b/fast-import.c index 32cd1f83e0..8e192c2991 100644 --- a/fast-import.c +++ b/fast-import.c @@ -1392,7 +1392,7 @@ static void read_next_command(void) static void cmd_mark(void) { - if (!strncmp("mark :", command_buf.buf, 6)) { + if (!(-prefixcmp(command_buf.buf, "mark :"))) { next_mark = strtoumax(command_buf.buf + 6, NULL, 10); read_next_command(); } @@ -1405,10 +1405,10 @@ static void *cmd_data (size_t *size) size_t length; char *buffer; - if (strncmp("data ", command_buf.buf, 5)) + if ((-prefixcmp(command_buf.buf, "data "))) die("Expected 'data n' command, found: %s", command_buf.buf); - if (!strncmp("<<", command_buf.buf + 5, 2)) { + if (!(-prefixcmp(command_buf.buf + 5, "<<"))) { char *term = xstrdup(command_buf.buf + 5 + 2); size_t sz = 8192, term_len = command_buf.len - 5 - 2; length = 0; @@ -1595,7 +1595,7 @@ static void file_change_m(struct branch *b) oe = find_mark(strtoumax(p + 1, &x, 10)); hashcpy(sha1, oe->sha1); p = x; - } else if (!strncmp("inline", p, 6)) { + } else if (!(-prefixcmp(p, "inline"))) { inline_data = 1; p += 6; } else { @@ -1668,7 +1668,7 @@ static void cmd_from(struct branch *b) const char *from; struct branch *s; - if (strncmp("from ", command_buf.buf, 5)) + if ((-prefixcmp(command_buf.buf, "from "))) return; if (b->branch_tree.tree) { @@ -1734,7 +1734,7 @@ static struct hash_list *cmd_merge(unsigned int *count) struct branch *s; *count = 0; - while (!strncmp("merge ", command_buf.buf, 6)) { + while (!(-prefixcmp(command_buf.buf, "merge "))) { from = strchr(command_buf.buf, ' ') + 1; n = xmalloc(sizeof(*n)); s = lookup_branch(from); @@ -1780,11 +1780,11 @@ static void cmd_new_commit(void) read_next_command(); cmd_mark(); - if (!strncmp("author ", command_buf.buf, 7)) { + if (!(-prefixcmp(command_buf.buf, "author "))) { author = parse_ident(command_buf.buf + 7); read_next_command(); } - if (!strncmp("committer ", command_buf.buf, 10)) { + if (!(-prefixcmp(command_buf.buf, "committer "))) { committer = parse_ident(command_buf.buf + 10); read_next_command(); } @@ -1805,9 +1805,9 @@ static void cmd_new_commit(void) for (;;) { if (1 == command_buf.len) break; - else if (!strncmp("M ", command_buf.buf, 2)) + else if (!(-prefixcmp(command_buf.buf, "M "))) file_change_m(b); - else if (!strncmp("D ", command_buf.buf, 2)) + else if (!(-prefixcmp(command_buf.buf, "D "))) file_change_d(b); else if (!strcmp("deleteall", command_buf.buf)) file_change_deleteall(b); @@ -1877,7 +1877,7 @@ static void cmd_new_tag(void) read_next_command(); /* from ... */ - if (strncmp("from ", command_buf.buf, 5)) + if ((-prefixcmp(command_buf.buf, "from "))) die("Expected from command, got %s", command_buf.buf); from = strchr(command_buf.buf, ' ') + 1; s = lookup_branch(from); @@ -1904,7 +1904,7 @@ static void cmd_new_tag(void) read_next_command(); /* tagger ... */ - if (strncmp("tagger ", command_buf.buf, 7)) + if ((-prefixcmp(command_buf.buf, "tagger "))) die("Expected tagger command, got %s", command_buf.buf); tagger = parse_ident(command_buf.buf + 7); @@ -1981,7 +1981,7 @@ int main(int argc, const char **argv) if (*a != '-' || !strcmp(a, "--")) break; - else if (!strncmp(a, "--date-format=", 14)) { + else if (!prefixcmp(a, "--date-format=")) { const char *fmt = a + 14; if (!strcmp(fmt, "raw")) whenspec = WHENSPEC_RAW; @@ -1992,15 +1992,15 @@ int main(int argc, const char **argv) else die("unknown --date-format argument %s", fmt); } - else if (!strncmp(a, "--max-pack-size=", 16)) + else if (!prefixcmp(a, "--max-pack-size=")) max_packsize = strtoumax(a + 16, NULL, 0) * 1024 * 1024; - else if (!strncmp(a, "--depth=", 8)) + else if (!prefixcmp(a, "--depth=")) max_depth = strtoul(a + 8, NULL, 0); - else if (!strncmp(a, "--active-branches=", 18)) + else if (!prefixcmp(a, "--active-branches=")) max_active_branches = strtoul(a + 18, NULL, 0); - else if (!strncmp(a, "--export-marks=", 15)) + else if (!prefixcmp(a, "--export-marks=")) mark_file = a + 15; - else if (!strncmp(a, "--export-pack-edges=", 20)) { + else if (!prefixcmp(a, "--export-pack-edges=")) { if (pack_edges) fclose(pack_edges); pack_edges = fopen(a + 20, "a"); @@ -2033,11 +2033,11 @@ int main(int argc, const char **argv) break; else if (!strcmp("blob", command_buf.buf)) cmd_new_blob(); - else if (!strncmp("commit ", command_buf.buf, 7)) + else if (!(-prefixcmp(command_buf.buf, "commit "))) cmd_new_commit(); - else if (!strncmp("tag ", command_buf.buf, 4)) + else if (!(-prefixcmp(command_buf.buf, "tag "))) cmd_new_tag(); - else if (!strncmp("reset ", command_buf.buf, 6)) + else if (!(-prefixcmp(command_buf.buf, "reset "))) cmd_reset_branch(); else if (!strcmp("checkpoint", command_buf.buf)) cmd_checkpoint(); diff --git a/fetch-pack.c b/fetch-pack.c index c787106764..1fd2c3adfb 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -198,13 +198,13 @@ static int find_common(int fd[2], unsigned char *result_sha1, int len; while ((len = packet_read_line(fd[0], line, sizeof(line)))) { - if (!strncmp("shallow ", line, 8)) { + if (!(-prefixcmp(line, "shallow "))) { if (get_sha1_hex(line + 8, sha1)) die("invalid shallow line: %s", line); register_shallow(sha1); continue; } - if (!strncmp("unshallow ", line, 10)) { + if (!(-prefixcmp(line, "unshallow "))) { if (get_sha1_hex(line + 10, sha1)) die("invalid unshallow line: %s", line); if (!lookup_object(sha1)) @@ -346,7 +346,7 @@ static void filter_refs(struct ref **refs, int nr_match, char **match) check_ref_format(ref->name + 5)) ; /* trash */ else if (fetch_all && - (!depth || strncmp(ref->name, "refs/tags/", 10) )) { + (!depth || prefixcmp(ref->name, "refs/tags/") )) { *newtail = ref; ref->next = NULL; newtail = &ref->next; @@ -683,11 +683,11 @@ int main(int argc, char **argv) char *arg = argv[i]; if (*arg == '-') { - if (!strncmp("--upload-pack=", arg, 14)) { + if (!(-prefixcmp(arg, "--upload-pack="))) { uploadpack = arg + 14; continue; } - if (!strncmp("--exec=", arg, 7)) { + if (!(-prefixcmp(arg, "--exec="))) { uploadpack = arg + 7; continue; } @@ -712,7 +712,7 @@ int main(int argc, char **argv) verbose = 1; continue; } - if (!strncmp("--depth=", arg, 8)) { + if (!(-prefixcmp(arg, "--depth="))) { depth = strtol(arg + 8, NULL, 0); if (stat(git_path("shallow"), &st)) st.st_mtime = 0; diff --git a/git.c b/git.c index 4dd196721f..1fad852064 100644 --- a/git.c +++ b/git.c @@ -48,7 +48,7 @@ static int handle_options(const char*** argv, int* argc) /* * Check remaining flags. */ - if (!strncmp(cmd, "--exec-path", 11)) { + if (!prefixcmp(cmd, "--exec-path")) { cmd += 11; if (*cmd == '=') git_set_exec_path(cmd + 1); @@ -66,7 +66,7 @@ static int handle_options(const char*** argv, int* argc) setenv(GIT_DIR_ENVIRONMENT, (*argv)[1], 1); (*argv)++; (*argc)--; - } else if (!strncmp(cmd, "--git-dir=", 10)) { + } else if (!prefixcmp(cmd, "--git-dir=")) { setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1); } else if (!strcmp(cmd, "--bare")) { static char git_dir[PATH_MAX+1]; @@ -88,7 +88,7 @@ static char *alias_string; static int git_alias_config(const char *var, const char *value) { - if (!strncmp(var, "alias.", 6) && !strcmp(var + 6, alias_command)) { + if (!prefixcmp(var, "alias.") && !strcmp(var + 6, alias_command)) { alias_string = xstrdup(value); } return 0; @@ -348,7 +348,7 @@ int main(int argc, const char **argv, char **envp) * So we just directly call the internal command handler, and * die if that one cannot handle it. */ - if (!strncmp(cmd, "git-", 4)) { + if (!prefixcmp(cmd, "git-")) { cmd += 4; argv[0] = cmd; handle_internal_command(argc, argv, envp); @@ -360,7 +360,7 @@ int main(int argc, const char **argv, char **envp) argc--; handle_options(&argv, &argc); if (argc > 0) { - if (!strncmp(argv[0], "--", 2)) + if (!prefixcmp(argv[0], "--")) argv[0] += 2; } else { /* Default command: "help" */ diff --git a/help.c b/help.c index b6674635a2..0893fea025 100644 --- a/help.c +++ b/help.c @@ -130,7 +130,7 @@ static void list_commands(const char *exec_path, const char *pattern) struct stat st; int entlen; - if (strncmp(de->d_name, "git-", 4)) + if (prefixcmp(de->d_name, "git-")) continue; strcpy(path+dirlen, de->d_name); if (stat(path, &st) || /* stat, not lstat */ @@ -179,7 +179,7 @@ static void show_man_page(const char *git_cmd) { const char *page; - if (!strncmp(git_cmd, "git", 3)) + if (!prefixcmp(git_cmd, "git")) page = git_cmd; else { int page_len = strlen(git_cmd) + 4; diff --git a/http-fetch.c b/http-fetch.c index 9f790a08e5..d9a4561c8c 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -717,7 +717,7 @@ static int fetch_indices(struct alt_base *repo) case 'P': i++; if (i + 52 <= buffer.posn && - !strncmp(data + i, " pack-", 6) && + !prefixcmp(data + i, " pack-") && !strncmp(data + i + 46, ".pack\n", 6)) { get_sha1_hex(data + i + 6, sha1); setup_index(repo, sha1); diff --git a/http-push.c b/http-push.c index b128c0146c..eb77c9af45 100644 --- a/http-push.c +++ b/http-push.c @@ -1060,7 +1060,7 @@ static int fetch_indices(void) case 'P': i++; if (i + 52 < buffer.posn && - !strncmp(data + i, " pack-", 6) && + !prefixcmp(data + i, " pack-") && !strncmp(data + i + 46, ".pack\n", 6)) { get_sha1_hex(data + i + 6, sha1); setup_index(sha1); @@ -1206,11 +1206,11 @@ static void handle_new_lock_ctx(struct xml_ctx *ctx, int tag_closed) lock->owner = xmalloc(strlen(ctx->cdata) + 1); strcpy(lock->owner, ctx->cdata); } else if (!strcmp(ctx->name, DAV_ACTIVELOCK_TIMEOUT)) { - if (!strncmp(ctx->cdata, "Second-", 7)) + if (!prefixcmp(ctx->cdata, "Second-")) lock->timeout = strtol(ctx->cdata + 7, NULL, 10); } else if (!strcmp(ctx->name, DAV_ACTIVELOCK_TOKEN)) { - if (!strncmp(ctx->cdata, "opaquelocktoken:", 16)) { + if (!prefixcmp(ctx->cdata, "opaquelocktoken:")) { lock->token = xmalloc(strlen(ctx->cdata) - 15); strcpy(lock->token, ctx->cdata + 16); } @@ -2168,7 +2168,7 @@ static void fetch_symref(const char *path, char **symref, unsigned char *sha1) return; /* If it's a symref, set the refname; otherwise try for a sha1 */ - if (!strncmp((char *)buffer.buffer, "ref: ", 5)) { + if (!prefixcmp((char *)buffer.buffer, "ref: ")) { *symref = xmalloc(buffer.posn - 5); strlcpy(*symref, (char *)buffer.buffer + 5, buffer.posn - 5); } else { diff --git a/index-pack.c b/index-pack.c index 72e0962415..fa9a0e7489 100644 --- a/index-pack.c +++ b/index-pack.c @@ -849,9 +849,9 @@ int main(int argc, char **argv) fix_thin_pack = 1; } else if (!strcmp(arg, "--keep")) { keep_msg = ""; - } else if (!strncmp(arg, "--keep=", 7)) { + } else if (!prefixcmp(arg, "--keep=")) { keep_msg = arg + 7; - } else if (!strncmp(arg, "--pack_header=", 14)) { + } else if (!prefixcmp(arg, "--pack_header=")) { struct pack_header *hdr; char *c; diff --git a/peek-remote.c b/peek-remote.c index ef3c76ce52..7b66228a22 100644 --- a/peek-remote.c +++ b/peek-remote.c @@ -35,11 +35,11 @@ int main(int argc, char **argv) char *arg = argv[i]; if (*arg == '-') { - if (!strncmp("--upload-pack=", arg, 14)) { + if (!(-prefixcmp(arg, "--upload-pack="))) { uploadpack = arg + 14; continue; } - if (!strncmp("--exec=", arg, 7)) { + if (!(-prefixcmp(arg, "--exec="))) { uploadpack = arg + 7; continue; } diff --git a/receive-pack.c b/receive-pack.c index 7311c822dd..7f1dcc045c 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -109,7 +109,7 @@ static int update(struct command *cmd) struct ref_lock *lock; cmd->error_string = NULL; - if (!strncmp(name, "refs/", 5) && check_ref_format(name + 5)) { + if (!prefixcmp(name, "refs/") && check_ref_format(name + 5)) { cmd->error_string = "funny refname"; return error("refusing to create funny ref '%s' locally", name); @@ -125,7 +125,7 @@ static int update(struct command *cmd) } if (deny_non_fast_forwards && !is_null_sha1(new_sha1) && !is_null_sha1(old_sha1) && - !strncmp(name, "refs/heads/", 11)) { + !prefixcmp(name, "refs/heads/")) { struct commit *old_commit, *new_commit; struct commit_list *bases, *ent; diff --git a/refs.c b/refs.c index 6387703789..d347876c87 100644 --- a/refs.c +++ b/refs.c @@ -828,8 +828,8 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg) goto rollback; } - if (!strncmp(oldref, "refs/heads/", 11) && - !strncmp(newref, "refs/heads/", 11)) { + if (!prefixcmp(oldref, "refs/heads/") && + !prefixcmp(newref, "refs/heads/")) { char oldsection[1024], newsection[1024]; snprintf(oldsection, 1024, "branch.%s", oldref + 11); @@ -894,8 +894,8 @@ static int log_ref_write(const char *ref_name, const unsigned char *old_sha1, log_file = git_path("logs/%s", ref_name); if (log_all_ref_updates && - (!strncmp(ref_name, "refs/heads/", 11) || - !strncmp(ref_name, "refs/remotes/", 13) || + (!prefixcmp(ref_name, "refs/heads/") || + !prefixcmp(ref_name, "refs/remotes/") || !strcmp(ref_name, "HEAD"))) { if (safe_create_leading_directories(log_file) < 0) return error("unable to create directory for %s", diff --git a/revision.c b/revision.c index 5b1794b0ef..abab3b9d01 100644 --- a/revision.c +++ b/revision.c @@ -813,11 +813,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch const char *arg = argv[i]; if (*arg == '-') { int opts; - if (!strncmp(arg, "--max-count=", 12)) { + if (!prefixcmp(arg, "--max-count=")) { revs->max_count = atoi(arg + 12); continue; } - if (!strncmp(arg, "--skip=", 7)) { + if (!prefixcmp(arg, "--skip=")) { revs->skip_count = atoi(arg + 7); continue; } @@ -836,27 +836,27 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->max_count = atoi(arg + 2); continue; } - if (!strncmp(arg, "--max-age=", 10)) { + if (!prefixcmp(arg, "--max-age=")) { revs->max_age = atoi(arg + 10); continue; } - if (!strncmp(arg, "--since=", 8)) { + if (!prefixcmp(arg, "--since=")) { revs->max_age = approxidate(arg + 8); continue; } - if (!strncmp(arg, "--after=", 8)) { + if (!prefixcmp(arg, "--after=")) { revs->max_age = approxidate(arg + 8); continue; } - if (!strncmp(arg, "--min-age=", 10)) { + if (!prefixcmp(arg, "--min-age=")) { revs->min_age = atoi(arg + 10); continue; } - if (!strncmp(arg, "--before=", 9)) { + if (!prefixcmp(arg, "--before=")) { revs->min_age = approxidate(arg + 9); continue; } - if (!strncmp(arg, "--until=", 8)) { + if (!prefixcmp(arg, "--until=")) { revs->min_age = approxidate(arg + 8); continue; } @@ -944,7 +944,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->num_ignore_packed = 0; continue; } - if (!strncmp(arg, "--unpacked=", 11)) { + if (!prefixcmp(arg, "--unpacked=")) { revs->unpacked = 1; add_ignore_packed(revs, arg+11); continue; @@ -980,7 +980,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->verbose_header = 1; continue; } - if (!strncmp(arg, "--pretty", 8)) { + if (!prefixcmp(arg, "--pretty")) { revs->verbose_header = 1; revs->commit_format = get_commit_format(arg+8); continue; @@ -1005,7 +1005,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->abbrev = DEFAULT_ABBREV; continue; } - if (!strncmp(arg, "--abbrev=", 9)) { + if (!prefixcmp(arg, "--abbrev=")) { revs->abbrev = strtoul(arg + 9, NULL, 10); if (revs->abbrev < MINIMUM_ABBREV) revs->abbrev = MINIMUM_ABBREV; @@ -1034,15 +1034,15 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch /* * Grepping the commit log */ - if (!strncmp(arg, "--author=", 9)) { + if (!prefixcmp(arg, "--author=")) { add_header_grep(revs, "author", arg+9); continue; } - if (!strncmp(arg, "--committer=", 12)) { + if (!prefixcmp(arg, "--committer=")) { add_header_grep(revs, "committer", arg+12); continue; } - if (!strncmp(arg, "--grep=", 7)) { + if (!prefixcmp(arg, "--grep=")) { add_message_grep(revs, arg+7); continue; } @@ -1050,7 +1050,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch all_match = 1; continue; } - if (!strncmp(arg, "--encoding=", 11)) { + if (!prefixcmp(arg, "--encoding=")) { arg += 11; if (strcmp(arg, "none")) git_log_output_encoding = strdup(arg); diff --git a/send-pack.c b/send-pack.c index 33e69dbe18..512b660e99 100644 --- a/send-pack.c +++ b/send-pack.c @@ -379,11 +379,11 @@ int main(int argc, char **argv) char *arg = *argv; if (*arg == '-') { - if (!strncmp(arg, "--receive-pack=", 15)) { + if (!prefixcmp(arg, "--receive-pack=")) { receivepack = arg + 15; continue; } - if (!strncmp(arg, "--exec=", 7)) { + if (!prefixcmp(arg, "--exec=")) { receivepack = arg + 7; continue; } diff --git a/setup.c b/setup.c index e9d3f5aab6..dda67d268d 100644 --- a/setup.c +++ b/setup.c @@ -251,7 +251,7 @@ const char *setup_git_directory_gently(int *nongit_ok) offset++; cwd[len++] = '/'; cwd[len] = 0; - inside_git_dir = !strncmp(cwd + offset, ".git/", 5); + inside_git_dir = !prefixcmp(cwd + offset, ".git/"); return cwd + offset; } diff --git a/shell.c b/shell.c index 8c08cf0fb3..c983fc7b86 100644 --- a/shell.c +++ b/shell.c @@ -8,7 +8,7 @@ static int do_generic_cmd(const char *me, char *arg) if (!arg || !(arg = sq_dequote(arg))) die("bad argument"); - if (strncmp(me, "git-", 4)) + if (prefixcmp(me, "git-")) die("bad command"); my_argv[0] = me + 4; diff --git a/upload-pack.c b/upload-pack.c index 3648aae1a7..d7876cade9 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -455,7 +455,7 @@ static int get_common_commits(void) continue; } len = strip(line, len); - if (!strncmp(line, "have ", 5)) { + if (!prefixcmp(line, "have ")) { switch (got_sha1(line+5, sha1)) { case -1: /* they have what we do not */ if (multi_ack && ok_to_give_up()) @@ -502,7 +502,7 @@ static void receive_needs(void) if (!len) break; - if (!strncmp("shallow ", line, 8)) { + if (!(-prefixcmp(line, "shallow "))) { unsigned char sha1[20]; struct object *object; use_thin_pack = 0; @@ -515,7 +515,7 @@ static void receive_needs(void) add_object_array(object, NULL, &shallows); continue; } - if (!strncmp("deepen ", line, 7)) { + if (!(-prefixcmp(line, "deepen "))) { char *end; use_thin_pack = 0; depth = strtol(line + 7, &end, 0); @@ -523,7 +523,7 @@ static void receive_needs(void) die("Invalid deepen: %s", line); continue; } - if (strncmp("want ", line, 5) || + if ((-prefixcmp(line, "want ")) || get_sha1_hex(line+5, sha1_buf)) die("git-upload-pack: protocol error, " "expected to get sha, not '%s'", line); @@ -656,7 +656,7 @@ int main(int argc, char **argv) strict = 1; continue; } - if (!strncmp(arg, "--timeout=", 10)) { + if (!prefixcmp(arg, "--timeout=")) { timeout = atoi(arg+10); continue; } diff --git a/wt-status.c b/wt-status.c index 2879c3d5ec..d17a6ba5b2 100644 --- a/wt-status.c +++ b/wt-status.c @@ -298,7 +298,7 @@ void wt_status_print(struct wt_status *s) if (s->branch) { const char *on_what = "On branch "; const char *branch_name = s->branch; - if (!strncmp(branch_name, "refs/heads/", 11)) + if (!prefixcmp(branch_name, "refs/heads/")) branch_name += 11; else if (!strcmp(branch_name, "HEAD")) { branch_name = ""; @@ -344,7 +344,7 @@ int git_status_config(const char *k, const char *v) wt_status_use_color = git_config_colorbool(k, v); return 0; } - if (!strncmp(k, "status.color.", 13) || !strncmp(k, "color.status.", 13)) { + if (!prefixcmp(k, "status.color.") || !strncmp(k, "color.status.", 13)) { int slot = parse_status_slot(k, 13); color_parse(v, k, wt_status_colors[slot]); } From 599065a3bb94ae9f48e3808b8fafc8443017af28 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 20 Feb 2007 01:54:00 -0800 Subject: [PATCH 037/201] prefixcmp(): fix-up mechanical conversion. Previous step converted use of strncmp() with literal string mechanically even when the result is only used as a boolean: if (!strncmp("foo", arg, 3)) ==> if (!(-prefixcmp(arg, "foo"))) This step manually cleans them up to read: if (!prefixcmp(arg, "foo")) Signed-off-by: Junio C Hamano --- builtin-archive.c | 2 +- builtin-blame.c | 6 +++--- builtin-grep.c | 6 +++--- builtin-pack-objects.c | 6 +++--- builtin-push.c | 4 ++-- builtin-rerere.c | 6 +++--- builtin-show-branch.c | 4 ++-- builtin-tar-tree.c | 2 +- daemon.c | 2 +- fast-import.c | 30 +++++++++++++++--------------- fetch-pack.c | 10 +++++----- peek-remote.c | 4 ++-- upload-pack.c | 6 +++--- 13 files changed, 44 insertions(+), 44 deletions(-) diff --git a/builtin-archive.c b/builtin-archive.c index 0c56de0698..8ea6cb1efc 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -35,7 +35,7 @@ static int run_remote_archiver(const char *remote, int argc, for (i = 1; i < argc; i++) { const char *arg = argv[i]; - if (!(-prefixcmp(arg, "--exec="))) { + if (!prefixcmp(arg, "--exec=")) { if (exec_at) die("multiple --exec specified"); exec = arg + 7; diff --git a/builtin-blame.c b/builtin-blame.c index db311bfba9..530b97f97d 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -2097,17 +2097,17 @@ int cmd_blame(int argc, const char **argv, const char *prefix) output_option |= OUTPUT_LONG_OBJECT_NAME; else if (!strcmp("-S", arg) && ++i < argc) revs_file = argv[i]; - else if (!(-prefixcmp(arg, "-M"))) { + else if (!prefixcmp(arg, "-M")) { opt |= PICKAXE_BLAME_MOVE; blame_move_score = parse_score(arg+2); } - else if (!(-prefixcmp(arg, "-C"))) { + else if (!prefixcmp(arg, "-C")) { if (opt & PICKAXE_BLAME_COPY) opt |= PICKAXE_BLAME_COPY_HARDER; opt |= PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE; blame_copy_score = parse_score(arg+2); } - else if (!(-prefixcmp(arg, "-L"))) { + else if (!prefixcmp(arg, "-L")) { if (!arg[2]) { if (++i >= argc) usage(blame_usage); diff --git a/builtin-grep.c b/builtin-grep.c index cec22047c9..f35f2d023c 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -527,9 +527,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) opt.word_regexp = 1; continue; } - if (!(-prefixcmp(arg, "-A")) || - !(-prefixcmp(arg, "-B")) || - !(-prefixcmp(arg, "-C")) || + if (!prefixcmp(arg, "-A") || + !prefixcmp(arg, "-B") || + !prefixcmp(arg, "-C") || (arg[0] == '-' && '1' <= arg[1] && arg[1] <= '9')) { unsigned num; const char *scan; diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 71113d8fb3..b5ed9ce2c8 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -1579,14 +1579,14 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) incremental = 1; continue; } - if (!(-prefixcmp(arg, "--window="))) { + if (!prefixcmp(arg, "--window=")) { char *end; window = strtoul(arg+9, &end, 0); if (!arg[9] || *end) usage(pack_usage); continue; } - if (!(-prefixcmp(arg, "--depth="))) { + if (!prefixcmp(arg, "--depth=")) { char *end; depth = strtoul(arg+8, &end, 0); if (!arg[8] || *end) @@ -1622,7 +1622,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) continue; } if (!strcmp("--unpacked", arg) || - !(-prefixcmp(arg, "--unpacked=")) || + !prefixcmp(arg, "--unpacked=") || !strcmp("--reflog", arg) || !strcmp("--all", arg)) { use_internal_rev_list = 1; diff --git a/builtin-push.c b/builtin-push.c index 2b98ba3231..979efcc45f 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -149,10 +149,10 @@ static int get_remotes_uri(const char *repo, const char *uri[MAX_URI]) int is_refspec; char *s, *p; - if (!(-prefixcmp(buffer, "URL:"))) { + if (!prefixcmp(buffer, "URL:")) { is_refspec = 0; s = buffer + 4; - } else if (!(-prefixcmp(buffer, "Push:"))) { + } else if (!prefixcmp(buffer, "Push:")) { is_refspec = 1; s = buffer + 5; } else diff --git a/builtin-rerere.c b/builtin-rerere.c index 978105b497..dd1d4c1c1d 100644 --- a/builtin-rerere.c +++ b/builtin-rerere.c @@ -105,11 +105,11 @@ static int handle_file(const char *path, SHA1_Init(&ctx); while (fgets(buf, sizeof(buf), f)) { - if (!(-prefixcmp(buf, "<<<<<<< "))) + if (!prefixcmp(buf, "<<<<<<< ")) hunk = 1; - else if (!(-prefixcmp(buf, "======="))) + else if (!prefixcmp(buf, "=======")) hunk = 2; - else if (!(-prefixcmp(buf, ">>>>>>> "))) { + else if (!prefixcmp(buf, ">>>>>>> ")) { hunk_no++; hunk = 0; if (memcmp(one->ptr, two->ptr, one->nr < two->nr ? diff --git a/builtin-show-branch.c b/builtin-show-branch.c index bf6aee49f7..402a8f7930 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -435,9 +435,9 @@ static int append_matching_ref(const char *refname, const unsigned char *sha1, i return 0; if (fnmatch(match_ref_pattern, tail, 0)) return 0; - if (!(-prefixcmp(refname, "refs/heads/"))) + if (!prefixcmp(refname, "refs/heads/")) return append_head_ref(refname, sha1, flag, cb_data); - if (!(-prefixcmp(refname, "refs/tags/"))) + if (!prefixcmp(refname, "refs/tags/")) return append_tag_ref(refname, sha1, flag, cb_data); return append_ref(refname, sha1, 0); } diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c index 28f8c1c164..b04719ef20 100644 --- a/builtin-tar-tree.c +++ b/builtin-tar-tree.c @@ -31,7 +31,7 @@ int cmd_tar_tree(int argc, const char **argv, const char *prefix) nargv[nargc++] = "git-archive"; nargv[nargc++] = "--format=tar"; - if (2 <= argc && !(-prefixcmp(argv[1], "--remote="))) { + if (2 <= argc && !prefixcmp(argv[1], "--remote=")) { nargv[nargc++] = argv[1]; argv++; argc--; diff --git a/daemon.c b/daemon.c index cdbc23f63d..e74ecac952 100644 --- a/daemon.c +++ b/daemon.c @@ -562,7 +562,7 @@ static int execute(struct sockaddr *addr) for (i = 0; i < ARRAY_SIZE(daemon_service); i++) { struct daemon_service *s = &(daemon_service[i]); int namelen = strlen(s->name); - if (!(-prefixcmp(line, "git-")) && + if (!prefixcmp(line, "git-") && !strncmp(s->name, line + 4, namelen) && line[namelen + 4] == ' ') { /* diff --git a/fast-import.c b/fast-import.c index 8e192c2991..5d040fdb00 100644 --- a/fast-import.c +++ b/fast-import.c @@ -1392,7 +1392,7 @@ static void read_next_command(void) static void cmd_mark(void) { - if (!(-prefixcmp(command_buf.buf, "mark :"))) { + if (!prefixcmp(command_buf.buf, "mark :")) { next_mark = strtoumax(command_buf.buf + 6, NULL, 10); read_next_command(); } @@ -1405,10 +1405,10 @@ static void *cmd_data (size_t *size) size_t length; char *buffer; - if ((-prefixcmp(command_buf.buf, "data "))) + if (prefixcmp(command_buf.buf, "data ")) die("Expected 'data n' command, found: %s", command_buf.buf); - if (!(-prefixcmp(command_buf.buf + 5, "<<"))) { + if (!prefixcmp(command_buf.buf + 5, "<<")) { char *term = xstrdup(command_buf.buf + 5 + 2); size_t sz = 8192, term_len = command_buf.len - 5 - 2; length = 0; @@ -1595,7 +1595,7 @@ static void file_change_m(struct branch *b) oe = find_mark(strtoumax(p + 1, &x, 10)); hashcpy(sha1, oe->sha1); p = x; - } else if (!(-prefixcmp(p, "inline"))) { + } else if (!prefixcmp(p, "inline")) { inline_data = 1; p += 6; } else { @@ -1668,7 +1668,7 @@ static void cmd_from(struct branch *b) const char *from; struct branch *s; - if ((-prefixcmp(command_buf.buf, "from "))) + if (prefixcmp(command_buf.buf, "from ")) return; if (b->branch_tree.tree) { @@ -1734,7 +1734,7 @@ static struct hash_list *cmd_merge(unsigned int *count) struct branch *s; *count = 0; - while (!(-prefixcmp(command_buf.buf, "merge "))) { + while (!prefixcmp(command_buf.buf, "merge ")) { from = strchr(command_buf.buf, ' ') + 1; n = xmalloc(sizeof(*n)); s = lookup_branch(from); @@ -1780,11 +1780,11 @@ static void cmd_new_commit(void) read_next_command(); cmd_mark(); - if (!(-prefixcmp(command_buf.buf, "author "))) { + if (!prefixcmp(command_buf.buf, "author ")) { author = parse_ident(command_buf.buf + 7); read_next_command(); } - if (!(-prefixcmp(command_buf.buf, "committer "))) { + if (!prefixcmp(command_buf.buf, "committer ")) { committer = parse_ident(command_buf.buf + 10); read_next_command(); } @@ -1805,9 +1805,9 @@ static void cmd_new_commit(void) for (;;) { if (1 == command_buf.len) break; - else if (!(-prefixcmp(command_buf.buf, "M "))) + else if (!prefixcmp(command_buf.buf, "M ")) file_change_m(b); - else if (!(-prefixcmp(command_buf.buf, "D "))) + else if (!prefixcmp(command_buf.buf, "D ")) file_change_d(b); else if (!strcmp("deleteall", command_buf.buf)) file_change_deleteall(b); @@ -1877,7 +1877,7 @@ static void cmd_new_tag(void) read_next_command(); /* from ... */ - if ((-prefixcmp(command_buf.buf, "from "))) + if (prefixcmp(command_buf.buf, "from ")) die("Expected from command, got %s", command_buf.buf); from = strchr(command_buf.buf, ' ') + 1; s = lookup_branch(from); @@ -1904,7 +1904,7 @@ static void cmd_new_tag(void) read_next_command(); /* tagger ... */ - if ((-prefixcmp(command_buf.buf, "tagger "))) + if (prefixcmp(command_buf.buf, "tagger ")) die("Expected tagger command, got %s", command_buf.buf); tagger = parse_ident(command_buf.buf + 7); @@ -2033,11 +2033,11 @@ int main(int argc, const char **argv) break; else if (!strcmp("blob", command_buf.buf)) cmd_new_blob(); - else if (!(-prefixcmp(command_buf.buf, "commit "))) + else if (!prefixcmp(command_buf.buf, "commit ")) cmd_new_commit(); - else if (!(-prefixcmp(command_buf.buf, "tag "))) + else if (!prefixcmp(command_buf.buf, "tag ")) cmd_new_tag(); - else if (!(-prefixcmp(command_buf.buf, "reset "))) + else if (!prefixcmp(command_buf.buf, "reset ")) cmd_reset_branch(); else if (!strcmp("checkpoint", command_buf.buf)) cmd_checkpoint(); diff --git a/fetch-pack.c b/fetch-pack.c index 1fd2c3adfb..41bdd27b8f 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -198,13 +198,13 @@ static int find_common(int fd[2], unsigned char *result_sha1, int len; while ((len = packet_read_line(fd[0], line, sizeof(line)))) { - if (!(-prefixcmp(line, "shallow "))) { + if (!prefixcmp(line, "shallow ")) { if (get_sha1_hex(line + 8, sha1)) die("invalid shallow line: %s", line); register_shallow(sha1); continue; } - if (!(-prefixcmp(line, "unshallow "))) { + if (!prefixcmp(line, "unshallow ")) { if (get_sha1_hex(line + 10, sha1)) die("invalid unshallow line: %s", line); if (!lookup_object(sha1)) @@ -683,11 +683,11 @@ int main(int argc, char **argv) char *arg = argv[i]; if (*arg == '-') { - if (!(-prefixcmp(arg, "--upload-pack="))) { + if (!prefixcmp(arg, "--upload-pack=")) { uploadpack = arg + 14; continue; } - if (!(-prefixcmp(arg, "--exec="))) { + if (!prefixcmp(arg, "--exec=")) { uploadpack = arg + 7; continue; } @@ -712,7 +712,7 @@ int main(int argc, char **argv) verbose = 1; continue; } - if (!(-prefixcmp(arg, "--depth="))) { + if (!prefixcmp(arg, "--depth=")) { depth = strtol(arg + 8, NULL, 0); if (stat(git_path("shallow"), &st)) st.st_mtime = 0; diff --git a/peek-remote.c b/peek-remote.c index 7b66228a22..96bfac498b 100644 --- a/peek-remote.c +++ b/peek-remote.c @@ -35,11 +35,11 @@ int main(int argc, char **argv) char *arg = argv[i]; if (*arg == '-') { - if (!(-prefixcmp(arg, "--upload-pack="))) { + if (!prefixcmp(arg, "--upload-pack=")) { uploadpack = arg + 14; continue; } - if (!(-prefixcmp(arg, "--exec="))) { + if (!prefixcmp(arg, "--exec=")) { uploadpack = arg + 7; continue; } diff --git a/upload-pack.c b/upload-pack.c index d7876cade9..804bbb6c9e 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -502,7 +502,7 @@ static void receive_needs(void) if (!len) break; - if (!(-prefixcmp(line, "shallow "))) { + if (!prefixcmp(line, "shallow ")) { unsigned char sha1[20]; struct object *object; use_thin_pack = 0; @@ -515,7 +515,7 @@ static void receive_needs(void) add_object_array(object, NULL, &shallows); continue; } - if (!(-prefixcmp(line, "deepen "))) { + if (!prefixcmp(line, "deepen ")) { char *end; use_thin_pack = 0; depth = strtol(line + 7, &end, 0); @@ -523,7 +523,7 @@ static void receive_needs(void) die("Invalid deepen: %s", line); continue; } - if ((-prefixcmp(line, "want ")) || + if (prefixcmp(line, "want ") || get_sha1_hex(line+5, sha1_buf)) die("git-upload-pack: protocol error, " "expected to get sha, not '%s'", line); From 1968d77dd632a9a9e5c6f5681649e5e65ed13088 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 20 Feb 2007 01:55:07 -0800 Subject: [PATCH 038/201] prefixcmp(): fix-up leftover strncmp(). There were instances of strncmp() that were formatted improperly (e.g. whitespace around parameter before closing parenthesis) that caused the earlier mechanical conversion step to miss them. This step cleans them up. Signed-off-by: Junio C Hamano --- builtin-ls-tree.c | 2 +- builtin-rev-parse.c | 2 +- builtin-show-branch.c | 4 ++-- diff.c | 2 +- http-fetch.c | 2 +- http-push.c | 2 +- imap-send.c | 8 ++++---- revision.c | 2 +- wt-status.c | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/builtin-ls-tree.c b/builtin-ls-tree.c index 201defd934..6472610ac2 100644 --- a/builtin-ls-tree.c +++ b/builtin-ls-tree.c @@ -118,7 +118,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) chomp_prefix = 0; break; } - if (!strncmp(argv[1]+2, "abbrev=",7)) { + if (!prefixcmp(argv[1]+2, "abbrev=")) { abbrev = strtoul(argv[1]+9, NULL, 10); if (abbrev && abbrev < MINIMUM_ABBREV) abbrev = MINIMUM_ABBREV; diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index a1c3411121..37addb25fa 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -233,7 +233,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) } continue; } - if (!strncmp(arg,"-n",2)) { + if (!prefixcmp(arg, "-n")) { if ((filter & DO_FLAGS) && (filter & DO_REVS)) show(arg); continue; diff --git a/builtin-show-branch.c b/builtin-show-branch.c index 402a8f7930..67ae6bacda 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -378,7 +378,7 @@ static int append_head_ref(const char *refname, const unsigned char *sha1, int f { unsigned char tmp[20]; int ofs = 11; - if (strncmp(refname, "refs/heads/", ofs)) + if (prefixcmp(refname, "refs/heads/")) return 0; /* If both heads/foo and tags/foo exists, get_sha1 would * get confused. @@ -392,7 +392,7 @@ static int append_remote_ref(const char *refname, const unsigned char *sha1, int { unsigned char tmp[20]; int ofs = 13; - if (strncmp(refname, "refs/remotes/", ofs)) + if (prefixcmp(refname, "refs/remotes/")) return 0; /* If both heads/foo and tags/foo exists, get_sha1 would * get confused. diff --git a/diff.c b/diff.c index fad13ab44b..c3afee2743 100644 --- a/diff.c +++ b/diff.c @@ -77,7 +77,7 @@ int git_diff_ui_config(const char *var, const char *value) diff_detect_rename_default = DIFF_DETECT_RENAME; return 0; } - if (!prefixcmp(var, "diff.color.") || !strncmp(var, "color.diff.", 11)) { + if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) { int slot = parse_diff_color_slot(var, 11); color_parse(value, var, diff_colors[slot]); return 0; diff --git a/http-fetch.c b/http-fetch.c index d9a4561c8c..e6cd11db73 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -718,7 +718,7 @@ static int fetch_indices(struct alt_base *repo) i++; if (i + 52 <= buffer.posn && !prefixcmp(data + i, " pack-") && - !strncmp(data + i + 46, ".pack\n", 6)) { + !prefixcmp(data + i + 46, ".pack\n")) { get_sha1_hex(data + i + 6, sha1); setup_index(repo, sha1); i += 51; diff --git a/http-push.c b/http-push.c index eb77c9af45..9ad6fd00b0 100644 --- a/http-push.c +++ b/http-push.c @@ -1061,7 +1061,7 @@ static int fetch_indices(void) i++; if (i + 52 < buffer.posn && !prefixcmp(data + i, " pack-") && - !strncmp(data + i + 46, ".pack\n", 6)) { + !prefixcmp(data + i + 46, ".pack\n")) { get_sha1_hex(data + i + 6, sha1); setup_index(sha1); i += 51; diff --git a/imap-send.c b/imap-send.c index 3eaf025720..84df2fabb7 100644 --- a/imap-send.c +++ b/imap-send.c @@ -1192,7 +1192,7 @@ count_messages( msg_data_t *msg ) char *p = msg->data; while (1) { - if (!strncmp( "From ", p, 5 )) { + if (!prefixcmp(p, "From ")) { count++; p += 5; } @@ -1216,7 +1216,7 @@ split_msg( msg_data_t *all_msgs, msg_data_t *msg, int *ofs ) data = &all_msgs->data[ *ofs ]; msg->len = all_msgs->len - *ofs; - if (msg->len < 5 || strncmp( data, "From ", 5 )) + if (msg->len < 5 || prefixcmp(data, "From ")) return 0; p = strchr( data, '\n' ); @@ -1267,12 +1267,12 @@ git_imap_config(const char *key, const char *val) imap_folder = xstrdup( val ); } else if (!strcmp( "host", key )) { { - if (!strncmp( "imap:", val, 5 )) + if (!prefixcmp(val, "imap:")) val += 5; if (!server.port) server.port = 143; } - if (!strncmp( "//", val, 2 )) + if (!prefixcmp(val, "//")) val += 2; server.host = xstrdup( val ); } diff --git a/revision.c b/revision.c index abab3b9d01..622afe3aa6 100644 --- a/revision.c +++ b/revision.c @@ -832,7 +832,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->max_count = atoi(argv[++i]); continue; } - if (!strncmp(arg,"-n",2)) { + if (!prefixcmp(arg, "-n")) { revs->max_count = atoi(arg + 2); continue; } diff --git a/wt-status.c b/wt-status.c index d17a6ba5b2..035e546ed7 100644 --- a/wt-status.c +++ b/wt-status.c @@ -344,7 +344,7 @@ int git_status_config(const char *k, const char *v) wt_status_use_color = git_config_colorbool(k, v); return 0; } - if (!prefixcmp(k, "status.color.") || !strncmp(k, "color.status.", 13)) { + if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) { int slot = parse_status_slot(k, 13); color_parse(v, k, wt_status_colors[slot]); } From c0f7a6c33da7ec875fb084c9d97e471cd3a14535 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 21 Feb 2007 01:24:57 -0500 Subject: [PATCH 039/201] git-gui: Include browser in our usage message. Now that the 'browser' subcommand can be used to startup the tree browser, it should be listed as a possible subcommand option in our usage message. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-gui.sh b/git-gui.sh index 405813069b..039e7bb93f 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -5478,7 +5478,7 @@ gui { # fall through to setup UI for commits } default { - puts stderr "usage: $argv0 \[{blame|citool}\]" + puts stderr "usage: $argv0 \[{blame|browser|citool}\]" exit 1 } } From 7391b2e9991e138786a2ac049ce7f276f7abce40 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 21 Feb 2007 01:29:05 -0500 Subject: [PATCH 040/201] git-gui: Remove TODO list. I'm apparently not very good at keeping my own TODO file current. I its also somewhat strange to keep the TODO list as part of the software branch, as its meta-information that is not directly related to the code. I'm pulling the TODO list from git-gui and moving it into a seperate branch. Signed-off-by: Shawn O. Pearce --- TODO | 44 -------------------------------------------- 1 file changed, 44 deletions(-) delete mode 100644 TODO diff --git a/TODO b/TODO deleted file mode 100644 index b95a137322..0000000000 --- a/TODO +++ /dev/null @@ -1,44 +0,0 @@ -Items outstanding: - - * Add file to .gitignore or info/excludes. - - * Populate the pull menu with local branches. - - * Make use of the new default merge data stored in repo-config. - - * Checkout a different local branch. - - * Push any local branch to a remote branch. - - * Merge any local branches through a real merge UI. - - * Allow user to define keyboard shortcuts for frequently used fetch - or merge operations. Or maybe just define a keyboard shortcut - for default fetch/default merge of current branch is enough; - but I do know a few users who merge a couple of common branches - also into the same branch so one default isn't quite enough. - - * Better organize fetch/push/pull console windows. - - * Clone UI (to download a new repository). - - * Remotes editor (for .git/config format only). - - * Show a shortlog of the last couple of commits in the main window, - to give the user warm fuzzy feelings that we have their data - saved. Actually this may be the set of commits not yet in - the upstream (aka default merge branch remote repository). - - * GUI configuration editor for options listed in - git.git/Documentation/config.txt. Ideally this would - parse that file and generate the options dialog from - the documentation itself, and include the help text - from the documentation as part of the UI somehow. - -Known bugs: - - * git-gui sometimes just closes on Windows with no error message. - I'm not sure what the problem is here. I suspect the wish - process is just terminating due to a segfault or something, - as the do_quit proc in git-gui doesn't run. It often seems to - occur while writing a commit message in the buffer. Odd. From 981193786fc30b9ee73b9f223a75642b4ed455b9 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 21 Feb 2007 01:33:59 -0500 Subject: [PATCH 041/201] git-gui: Don't crash in citool mode on initial commit. Attempting to use `git citool` to create an initial commit caused git-gui to crash with a Tcl error as it tried to add the newly born branch to the non-existant branch menu. Moving this code to after the normal commit cleanup logic resolves the issue, as we only have a branch menu if we are not in singlecommit mode. Signed-off-by: Shawn O. Pearce --- git-gui.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 039e7bb93f..f84ba3382b 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1318,14 +1318,6 @@ proc commit_committree {fd_wt curHEAD msg} { return } - # -- Make sure our current branch exists. - # - if {$commit_type eq {initial}} { - lappend all_heads $current_branch - set all_heads [lsort -unique $all_heads] - populate_branch_menu - } - # -- Cleanup after ourselves. # catch {file delete $msg_p} @@ -1361,6 +1353,14 @@ proc commit_committree {fd_wt curHEAD msg} { if {[is_enabled singlecommit]} do_quit + # -- Make sure our current branch exists. + # + if {$commit_type eq {initial}} { + lappend all_heads $current_branch + set all_heads [lsort -unique $all_heads] + populate_branch_menu + } + # -- Update in memory status # set selected_commit_type new From 34c6a82b8afca18eab28eeca659e29db0faabc62 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 19 Feb 2007 15:56:04 +0100 Subject: [PATCH 042/201] git grep: use pager Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- git.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git.c b/git.c index 1fad852064..83f3d90ee3 100644 --- a/git.c +++ b/git.c @@ -247,7 +247,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "fsck", cmd_fsck, RUN_SETUP }, { "fsck-objects", cmd_fsck, RUN_SETUP }, { "get-tar-commit-id", cmd_get_tar_commit_id }, - { "grep", cmd_grep, RUN_SETUP }, + { "grep", cmd_grep, RUN_SETUP | USE_PAGER }, { "help", cmd_help }, { "init", cmd_init_db }, { "init-db", cmd_init_db }, From b97e911643341cb31e6b97029b9ffd96fc675b1d Mon Sep 17 00:00:00 2001 From: Martin Waitz Date: Sat, 17 Feb 2007 10:13:10 +0100 Subject: [PATCH 043/201] Support for large files on 32bit systems. Glibc uses the same size for int and off_t by default. In order to support large pack sizes (>2GB) we force Glibc to a 64bit off_t. Signed-off-by: Martin Waitz Signed-off-by: Junio C Hamano --- git-compat-util.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/git-compat-util.h b/git-compat-util.h index d027c36b97..5d154faef6 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -1,6 +1,8 @@ #ifndef GIT_COMPAT_UTIL_H #define GIT_COMPAT_UTIL_H +#define _FILE_OFFSET_BITS 64 + #ifndef FLEX_ARRAY #if defined(__GNUC__) && (__GNUC__ < 3) #define FLEX_ARRAY 0 From d2cd696322011eb13125930e747f0d2a7778b992 Mon Sep 17 00:00:00 2001 From: Alex Riesen Date: Tue, 20 Feb 2007 10:04:32 +0100 Subject: [PATCH 044/201] disable t4016-diff-quote.sh on some filesystems ... because the filesystems (most typically FAT and NTFS) do not support HT nor LF in filenames. Signed-off-by: Alex Riesen --- t/t4016-diff-quote.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/t/t4016-diff-quote.sh b/t/t4016-diff-quote.sh index edde8f5568..2e7cd5f255 100755 --- a/t/t4016-diff-quote.sh +++ b/t/t4016-diff-quote.sh @@ -13,6 +13,10 @@ P1='pathname with HT' P2='pathname with SP' P3='pathname with LF' +: >"$P1" 2>&1 && test -f "$P1" && rm -f "$P1" || { + echo >&2 'Filesystem does not support tabs in names' + test_done +} test_expect_success setup ' echo P0.0 >"$P0.0" && From 4a6b9bb60ab3cdb3a749ec43763845a7a60d40d4 Mon Sep 17 00:00:00 2001 From: Simon 'corecode' Schubert Date: Sun, 18 Feb 2007 18:17:08 +0100 Subject: [PATCH 045/201] Allow passing of an alternative CVSROOT via -d. This is necessary if using CVS in an asymmetric fashion, i.e. when the CVSROOT you are checking out from differs from the CVSROOT you have to commit to. Signed-off-by: Simon 'corecode' Schubert Signed-off-by: Junio C Hamano --- Documentation/git-cvsexportcommit.txt | 7 ++++++- git-cvsexportcommit.perl | 27 +++++++++++++++++---------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/Documentation/git-cvsexportcommit.txt b/Documentation/git-cvsexportcommit.txt index 27d531b888..555b8234f0 100644 --- a/Documentation/git-cvsexportcommit.txt +++ b/Documentation/git-cvsexportcommit.txt @@ -8,7 +8,7 @@ git-cvsexportcommit - Export a single commit to a CVS checkout SYNOPSIS -------- -'git-cvsexportcommit' [-h] [-v] [-c] [-P] [-p] [-a] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID +'git-cvsexportcommit' [-h] [-v] [-c] [-P] [-p] [-a] [-d cvsroot] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID DESCRIPTION @@ -43,6 +43,11 @@ OPTIONS Add authorship information. Adds Author line, and Committer (if different from Author) to the message. +-d:: + Set an alternative CVSROOT to use. This corresponds to the CVS + -d parameter. Usually users will not want to set this, except + if using CVS in an asymmetric fashion. + -f:: Force the merge even if the files are not up to date. diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index 870554eade..d08216cfd7 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -15,14 +15,21 @@ die "GIT_DIR is not defined or is unreadable"; } -our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m ); +our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m, $opt_d); -getopts('hPpvcfam:'); +getopts('hPpvcfam:d:'); $opt_h && usage(); die "Need at least one commit identifier!" unless @ARGV; +my @cvs; +if ($opt_d) { + @cvs = ('cvs', '-d', $opt_d); +} else { + @cvs = ('cvs'); +} + # setup a tempdir our ($tmpdir, $tmpdirname) = tempdir('git-cvsapplycommit-XXXXXX', TMPDIR => 1, @@ -160,7 +167,7 @@ my $p = $1; next if (grep { $_ eq $p } @dirs); } - my @status = grep(m/^File/, safe_pipe_capture('cvs', '-q', 'status' ,$f)); + my @status = grep(m/^File/, safe_pipe_capture(@cvs, '-q', 'status' ,$f)); if (@status > 1) { warn 'Strange! cvs status returned more than one line?'}; if (-d dirname $f and $status[0] !~ m/Status: Unknown$/ and $status[0] !~ m/^File: no file /) { @@ -173,7 +180,7 @@ foreach my $f (@files) { next if grep { $_ eq $f } @afiles; # TODO:we need to handle removed in cvs - my @status = grep(m/^File/, safe_pipe_capture('cvs', '-q', 'status' ,$f)); + my @status = grep(m/^File/, safe_pipe_capture(@cvs, '-q', 'status' ,$f)); if (@status > 1) { warn 'Strange! cvs status returned more than one line?'}; unless ($status[0] =~ m/Status: Up-to-date$/) { $dirty = 1; @@ -194,7 +201,7 @@ print "Patch applied successfully. Adding new files and directories to CVS\n"; my $dirtypatch = 0; foreach my $d (@dirs) { - if (system('cvs','add',$d)) { + if (system(@cvs,'add',$d)) { $dirtypatch = 1; warn "Failed to cvs add directory $d -- you may need to do it manually"; } @@ -202,9 +209,9 @@ foreach my $f (@afiles) { if (grep { $_ eq $f } @bfiles) { - system('cvs', 'add','-kb',$f); + system(@cvs, 'add','-kb',$f); } else { - system('cvs', 'add', $f); + system(@cvs, 'add', $f); } if ($?) { $dirtypatch = 1; @@ -213,7 +220,7 @@ } foreach my $f (@dfiles) { - system('cvs', 'rm', '-f', $f); + system(@cvs, 'rm', '-f', $f); if ($?) { $dirtypatch = 1; warn "Failed to cvs rm -f $f -- you may need to do it manually"; @@ -223,7 +230,7 @@ print "Commit to CVS\n"; print "Patch title (first comment line): $title\n"; my @commitfiles = map { unless (m/\s/) { '\''.$_.'\''; } else { $_; }; } (@files); -my $cmd = "cvs commit -F .msg @commitfiles"; +my $cmd = join(' ', @cvs)." commit -F .msg @commitfiles"; if ($dirtypatch) { print "NOTE: One or more hunks failed to apply cleanly.\n"; @@ -236,7 +243,7 @@ if ($opt_c) { print "Autocommit\n $cmd\n"; - print safe_pipe_capture('cvs', 'commit', '-F', '.msg', @files); + print safe_pipe_capture(@cvs, 'commit', '-F', '.msg', @files); if ($?) { die "Exiting: The commit did not succeed"; } From 7b9a13ece8a1e7c0fd2a82d4bd4fcf0a9ce0e8c4 Mon Sep 17 00:00:00 2001 From: Theodore Ts'o Date: Tue, 20 Feb 2007 15:13:42 -0500 Subject: [PATCH 046/201] Add config_boolean() method to the Git perl module Signed-off-by: "Theodore Ts'o" Signed-off-by: Junio C Hamano --- perl/Git.pm | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/perl/Git.pm b/perl/Git.pm index f2c156cde9..b5b1cf5edc 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -516,6 +516,36 @@ sub config { } +=item config_boolean ( VARIABLE ) + +Retrieve the boolean configuration C. + +Must be called on a repository instance. + +This currently wraps command('config') so it is not so fast. + +=cut + +sub config_boolean { + my ($self, $var) = @_; + $self->repo_path() + or throw Error::Simple("not a repository"); + + try { + return $self->command_oneline('config', '--bool', '--get', + $var); + } catch Git::Error::Command with { + my $E = shift; + if ($E->value() == 1) { + # Key not found. + return undef; + } else { + throw $E; + } + }; +} + + =item ident ( TYPE | IDENTSTR ) =item ident_person ( TYPE | IDENTSTR | IDENTARRAY ) From 1918278ea1019553b01297aad2caeed88e9092a4 Mon Sep 17 00:00:00 2001 From: Theodore Ts'o Date: Tue, 20 Feb 2007 15:13:43 -0500 Subject: [PATCH 047/201] Allow git-remote to update named groups of remotes In response to a feature request from Shawn Pearce, this patch allows a user to update a named group of remotes by using "git remote update ", where the group is defined in the config file by remotes.. The default if the named group is not specified is now fetched group remotes.default, instead of remote.fetch, which is what had been previously used. In addition, if remotes.default is not defined, all remotes defined in the config file will be used, as before, but there is now also possible to request that a particular repository to be skipped by default by using the boolean configuration parameter remote..skipDefaultUpdate. Signed-off-by: "Theodore Ts'o" Signed-off-by: Junio C Hamano --- Documentation/config.txt | 12 +++++++---- Documentation/git-remote.txt | 11 ++++++---- git-remote.perl | 40 ++++++++++++++++++++++++++++-------- 3 files changed, 46 insertions(+), 17 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index d8e696f4cd..f5c846f410 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -439,10 +439,6 @@ pull.octopus:: pull.twohead:: The default merge strategy to use when pulling a single branch. -remote.fetch:: - The list of remotes which are fetched by "git remote update". - See gitlink:git-remote[1]. - remote..url:: The URL of a remote repository. See gitlink:git-fetch[1] or gitlink:git-push[1]. @@ -455,6 +451,10 @@ remote..push:: The default set of "refspec" for gitlink:git-push[1]. See gitlink:git-push[1]. +remote..skipDefaultUpdate:: + If true, this remote will be skipped by default when updating + using the remote subcommand of gitlink:git-remote[1]. + remote..receivepack:: The default program to execute on the remote side when pushing. See option \--exec of gitlink:git-push[1]. @@ -463,6 +463,10 @@ remote..uploadpack:: The default program to execute on the remote side when fetching. See option \--exec of gitlink:git-fetch-pack[1]. +remotes.:: + The list of remotes which are fetched by "git remote update + ". See gitlink:git-remote[1]. + repack.usedeltabaseoffset:: Allow gitlink:git-repack[1] to create packs that uses delta-base offset. Defaults to false. diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index 06ba2e6f26..250761f97e 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -13,7 +13,7 @@ SYNOPSIS 'git-remote' add 'git-remote' show 'git-remote' prune -'git-remote' update +'git-remote' update [group] DESCRIPTION ----------- @@ -46,9 +46,12 @@ referenced by , but are still locally available in 'update':: -Fetch updates for the remotes in the repository. By default all remotes -are updated, but this can be configured via the configuration parameter -'remote.fetch'. (See gitlink:git-config[1]). +Fetch updates for a named set of remotes in the repository as defined by +remotes.. If a named group is not specified on the command line, +the configuration parameter remotes.default will get used; if +remotes.default is not defined, all remotes which do not the +configuration parameter remote..skipDefaultUpdate set to true will +be updated. (See gitlink:git-config[1]). DISCUSSION diff --git a/git-remote.perl b/git-remote.perl index 6e473ecfd0..61244e9782 100755 --- a/git-remote.perl +++ b/git-remote.perl @@ -274,6 +274,31 @@ sub add_remote { } } +sub update_remote { + my ($name) = @_; + + my $conf = $git->config("remotes." . $name); + if (defined($conf)) { + @remotes = split(' ', $conf); + } elsif ($name eq 'default') { + undef @remotes; + for (sort keys %$remote) { + my $do_fetch = $git->config_boolean("remote." . $_ . + ".skipDefaultUpdate"); + if (!defined($do_fetch) || $do_fetch ne "true") { + push @remotes, $_; + } + } + } else { + print STDERR "Remote group $name does not exists.\n"; + exit(1); + } + for (@remotes) { + print "Updating $_\n"; + $git->command('fetch', "$_"); + } +} + sub add_usage { print STDERR "Usage: git remote add [-f] [-t track]* [-m master] \n"; exit(1); @@ -304,15 +329,12 @@ sub add_usage { } } elsif ($ARGV[0] eq 'update') { - my $conf = $git->config("remote.fetch"); - if (defined($conf)) { - @remotes = split(' ', $conf); - } else { - @remotes = sort keys %$remote; + if (@ARGV <= 1) { + update_remote("default"); + exit(1); } - for (@remotes) { - print "Fetching $_\n"; - $git->command('fetch', "$_"); + for ($i = 1; $i < @ARGV; $i++) { + update_remote($ARGV[$i]); } } elsif ($ARGV[0] eq 'prune') { @@ -372,6 +394,6 @@ sub add_usage { print STDERR " git remote add \n"; print STDERR " git remote show \n"; print STDERR " git remote prune \n"; - print STDERR " git remote update\n"; + print STDERR " git remote update [group]\n"; exit(1); } From 13e36ec51bee59c6433322b23323b75e9a635d35 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 20 Feb 2007 15:08:46 +0100 Subject: [PATCH 048/201] Teach diff -B about colours Matthias Lederhofer noticed that `diff -B` did not pick up on diff colournig. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- diff.c | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/diff.c b/diff.c index c3afee2743..019ecbc1fa 100644 --- a/diff.c +++ b/diff.c @@ -184,30 +184,40 @@ static void print_line_count(int count) } } -static void copy_file(int prefix, const char *data, int size) +static void copy_file(int prefix, const char *data, int size, + const char *set, const char *reset) { int ch, nl_just_seen = 1; while (0 < size--) { ch = *data++; - if (nl_just_seen) + if (nl_just_seen) { + fputs(set, stdout); putchar(prefix); - putchar(ch); - if (ch == '\n') + } + if (ch == '\n') { nl_just_seen = 1; - else + fputs(reset, stdout); + } else nl_just_seen = 0; + putchar(ch); } if (!nl_just_seen) - printf("\n\\ No newline at end of file\n"); + printf("%s\n\\ No newline at end of file\n", reset); } static void emit_rewrite_diff(const char *name_a, const char *name_b, struct diff_filespec *one, - struct diff_filespec *two) + struct diff_filespec *two, + int color_diff) { int lc_a, lc_b; const char *name_a_tab, *name_b_tab; + const char *metainfo = diff_get_color(color_diff, DIFF_METAINFO); + const char *fraginfo = diff_get_color(color_diff, DIFF_FRAGINFO); + const char *old = diff_get_color(color_diff, DIFF_FILE_OLD); + const char *new = diff_get_color(color_diff, DIFF_FILE_NEW); + const char *reset = diff_get_color(color_diff, DIFF_RESET); name_a_tab = strchr(name_a, ' ') ? "\t" : ""; name_b_tab = strchr(name_b, ' ') ? "\t" : ""; @@ -216,17 +226,17 @@ static void emit_rewrite_diff(const char *name_a, diff_populate_filespec(two, 0); lc_a = count_lines(one->data, one->size); lc_b = count_lines(two->data, two->size); - printf("--- a/%s%s\n+++ b/%s%s\n@@ -", - name_a, name_a_tab, - name_b, name_b_tab); + printf("%s--- a/%s%s%s\n%s+++ b/%s%s%s\n%s@@ -", + metainfo, name_a, name_a_tab, reset, + metainfo, name_b, name_b_tab, reset, fraginfo); print_line_count(lc_a); printf(" +"); print_line_count(lc_b); - printf(" @@\n"); + printf(" @@%s\n", reset); if (lc_a) - copy_file('-', one->data, one->size); + copy_file('-', one->data, one->size, old, reset); if (lc_b) - copy_file('+', two->data, two->size); + copy_file('+', two->data, two->size, new, reset); } static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) @@ -1084,7 +1094,8 @@ static void builtin_diff(const char *name_a, if ((one->mode ^ two->mode) & S_IFMT) goto free_ab_and_return; if (complete_rewrite) { - emit_rewrite_diff(name_a, name_b, one, two); + emit_rewrite_diff(name_a, name_b, one, two, + o->color_diff); goto free_ab_and_return; } } From b5a40a57240cc292c32e691d3b3c550f6cf8d98c Mon Sep 17 00:00:00 2001 From: Pavel Roskin Date: Wed, 21 Feb 2007 00:03:36 -0500 Subject: [PATCH 049/201] git-remote: support remotes with a dot in the name [jc: the original from Pavel was limiting the variable names to only fetch and url, but I loosened it to take valid variable names.] Signed-off-by: Pavel Roskin Signed-off-by: Junio C Hamano --- git-remote.perl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-remote.perl b/git-remote.perl index 61244e9782..bd70bf1ddd 100755 --- a/git-remote.perl +++ b/git-remote.perl @@ -67,7 +67,7 @@ sub list_remote { $git->command(qw(config --get-regexp), '^remote\.'); }; for (@remotes) { - if (/^remote\.([^.]*)\.(\S*)\s+(.*)$/) { + if (/^remote\.(\S+?)\.([^.\s]+)\s+(.*)$/) { add_remote_config(\%seen, $1, $2, $3); } } From c24e9757e9f608ad3985ee9093d28ca5cd37f052 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 21 Feb 2007 01:14:22 -0800 Subject: [PATCH 050/201] t4119: add test for traditional patch and different p_value Signed-off-by: Junio C Hamano --- t/t4119-apply-config.sh | 53 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/t/t4119-apply-config.sh b/t/t4119-apply-config.sh index 816b5b8fb2..f9b9425153 100755 --- a/t/t4119-apply-config.sh +++ b/t/t4119-apply-config.sh @@ -18,6 +18,15 @@ test_expect_success setup ' git diff >patch.file ' +# Also handcraft GNU diff output; note this has trailing whitespace. +cat >gpatch.file <<\EOF +--- file1 2007-02-21 01:04:24.000000000 -0800 ++++ file1+ 2007-02-21 01:07:44.000000000 -0800 +@@ -1 +1 @@ +-A ++B +EOF + test_expect_success 'apply --whitespace=strip' ' rm -f sub/file1 && @@ -29,8 +38,12 @@ test_expect_success 'apply --whitespace=strip' ' then echo "Eh?" false - else + elif grep B sub/file1 + then echo Happy + else + echo "Huh?" + false fi ' @@ -46,6 +59,9 @@ test_expect_success 'apply --whitespace=strip from config' ' then echo "Eh?" false + elif grep B sub/file1 + then + echo Happy else echo Happy fi @@ -67,8 +83,12 @@ test_expect_success 'apply --whitespace=strip in subdir' ' then echo "Eh?" false - else + elif grep B file1 + then echo Happy + else + echo "Huh?" + false fi ' @@ -86,8 +106,35 @@ test_expect_success 'apply --whitespace=strip from config in subdir' ' then echo "Eh?" false - else + elif grep B file1 + then echo Happy + else + echo "Huh?" + false + fi +' + +test_expect_success 'same in subdir but with traditional patch input' ' + + cd "$D" && + git config apply.whitespace strip && + rm -f sub/file1 && + cp saved sub/file1 && + git update-index --refresh && + + cd sub && + git apply -p0 ../gpatch.file && + if grep " " file1 + then + echo "Eh?" + false + elif grep B file1 + then + echo Happy + else + echo "Huh?" + false fi ' From 6c912f5b04e3216a5487e03235a8454b754a464e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 21 Feb 2007 00:58:18 -0800 Subject: [PATCH 051/201] Fix botched "leak fix" When (new_name == old_name), the previous one prefixed old_name alone, leaving new_name untouched, and worse yet, left it dangling pointing at an already freed memory location. Signed-off-by: Junio C Hamano --- builtin-apply.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/builtin-apply.c b/builtin-apply.c index 2a40af3ff0..1beebe5ff1 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -2516,9 +2516,15 @@ static void prefix_patches(struct patch *p) if (!prefix) return; for ( ; p; p = p->next) { - if (p->new_name != p->old_name) + if (p->new_name == p->old_name) { + char *prefixed = p->new_name; + prefix_one(&prefixed); + p->new_name = p->old_name = prefixed; + } + else { prefix_one(&p->new_name); - prefix_one(&p->old_name); + prefix_one(&p->old_name); + } } } From c750da256a54f189de28d3deb054328d67f9b9be Mon Sep 17 00:00:00 2001 From: Michael Loeffler Date: Wed, 14 Feb 2007 17:03:12 +0100 Subject: [PATCH 052/201] Use gunzip -c over gzcat in import-tars example. Not everyone has gzcat or bzcat installed on their system, but gunzip -c and bunzip2 -c perform the same task and are available if the user has installed gzip support or bzip2 support. Signed-off-by: Michael Loeffler Signed-off-by: Shawn O. Pearce --- contrib/fast-import/import-tars.perl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/contrib/fast-import/import-tars.perl b/contrib/fast-import/import-tars.perl index 990c9e70b2..5585a8b2c5 100755 --- a/contrib/fast-import/import-tars.perl +++ b/contrib/fast-import/import-tars.perl @@ -25,11 +25,14 @@ my $tar_name = $1; if ($tar_name =~ s/\.(tar\.gz|tgz)$//) { - open(I, '-|', 'gzcat', $tar_file) or die "Unable to gzcat $tar_file: $!\n"; + open(I, '-|', 'gunzip', '-c', $tar_file) + or die "Unable to gunzip -c $tar_file: $!\n"; } elsif ($tar_name =~ s/\.(tar\.bz2|tbz2)$//) { - open(I, '-|', 'bzcat', $tar_file) or die "Unable to bzcat $tar_file: $!\n"; + open(I, '-|', 'bunzip2', '-c', $tar_file) + or die "Unable to bunzip2 -c $tar_file: $!\n"; } elsif ($tar_name =~ s/\.tar\.Z$//) { - open(I, '-|', 'zcat', $tar_file) or die "Unable to zcat $tar_file: $!\n"; + open(I, '-|', 'uncompress', '-c', $tar_file) + or die "Unable to uncompress -c $tar_file: $!\n"; } elsif ($tar_name =~ s/\.tar$//) { open(I, $tar_file) or die "Unable to open $tar_file: $!\n"; } else { From 9987d7c58a847ab1605ae3216ff1ca95b19f0ad1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 21 Feb 2007 14:31:10 -0800 Subject: [PATCH 053/201] git-apply: notice "diff --git" patch again Earlier one that tried to be too consistent with GNU patch by not stripping the leading path when we _know_ we are in a subdirectory and the patch is relative to the toplevel was a mistake. This fixes it. - No change to behaviour when it is run from the toplevel of the repository. - When run from a subdirectory to apply a git-generated patch, it uses the right -p value automatically, with or without --index nor --cached option. - When run from a subdirectory to apply a randomly generated patch, it wants the right -p value to be given by the user. The second one is a pure improvement to correct inconsistency between --index and non --index case, compared with 1.5.0. The third point could be further improved to guess what the right value for -p should be by looking at the patch, but should be a topic of a separate patch. Signed-off-by: Junio C Hamano --- builtin-apply.c | 23 ++++++++++++++++------- t/t4119-apply-config.sh | 4 ++-- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/builtin-apply.c b/builtin-apply.c index 1beebe5ff1..12f00e38db 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -144,6 +144,7 @@ struct patch { unsigned long deflate_origlen; int lines_added, lines_deleted; int score; + unsigned int is_toplevel_relative:1; unsigned int inaccurate_eof:1; unsigned int is_binary:1; unsigned int is_copy:1; @@ -362,7 +363,7 @@ static int gitdiff_hdrend(const char *line, struct patch *patch) static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew) { if (!orig_name && !isnull) - return find_name(line, NULL, p_value, TERM_TAB); + return find_name(line, NULL, 1, TERM_TAB); if (orig_name) { int len; @@ -372,7 +373,7 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, len = strlen(name); if (isnull) die("git-apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr); - another = find_name(line, NULL, p_value, TERM_TAB); + another = find_name(line, NULL, 1, TERM_TAB); if (!another || memcmp(another, name, len)) die("git-apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr); free(another); @@ -427,28 +428,28 @@ static int gitdiff_newfile(const char *line, struct patch *patch) static int gitdiff_copysrc(const char *line, struct patch *patch) { patch->is_copy = 1; - patch->old_name = find_name(line, NULL, p_value-1, 0); + patch->old_name = find_name(line, NULL, 0, 0); return 0; } static int gitdiff_copydst(const char *line, struct patch *patch) { patch->is_copy = 1; - patch->new_name = find_name(line, NULL, p_value-1, 0); + patch->new_name = find_name(line, NULL, 0, 0); return 0; } static int gitdiff_renamesrc(const char *line, struct patch *patch) { patch->is_rename = 1; - patch->old_name = find_name(line, NULL, p_value-1, 0); + patch->old_name = find_name(line, NULL, 0, 0); return 0; } static int gitdiff_renamedst(const char *line, struct patch *patch) { patch->is_rename = 1; - patch->new_name = find_name(line, NULL, p_value-1, 0); + patch->new_name = find_name(line, NULL, 0, 0); return 0; } @@ -787,6 +788,7 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc { unsigned long offset, len; + patch->is_toplevel_relative = 0; patch->is_rename = patch->is_copy = 0; patch->is_new = patch->is_delete = -1; patch->old_mode = patch->new_mode = 0; @@ -831,6 +833,7 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc die("git diff header lacks filename information (line %d)", linenr); patch->old_name = patch->new_name = patch->def_name; } + patch->is_toplevel_relative = 1; *hdrsize = git_hdr_len; return offset; } @@ -2499,6 +2502,12 @@ static int use_patch(struct patch *p) return 0; x = x->next; } + if (0 < prefix_length) { + int pathlen = strlen(pathname); + if (pathlen <= prefix_length || + memcmp(prefix, pathname, prefix_length)) + return 0; + } return 1; } @@ -2513,7 +2522,7 @@ static void prefix_one(char **name) static void prefix_patches(struct patch *p) { - if (!prefix) + if (!prefix || p->is_toplevel_relative) return; for ( ; p; p = p->next) { if (p->new_name == p->old_name) { diff --git a/t/t4119-apply-config.sh b/t/t4119-apply-config.sh index f9b9425153..32e0d7172e 100755 --- a/t/t4119-apply-config.sh +++ b/t/t4119-apply-config.sh @@ -78,7 +78,7 @@ test_expect_success 'apply --whitespace=strip in subdir' ' git update-index --refresh && cd sub && - git apply --whitespace=strip -p2 ../patch.file && + git apply --whitespace=strip ../patch.file && if grep " " file1 then echo "Eh?" @@ -101,7 +101,7 @@ test_expect_success 'apply --whitespace=strip from config in subdir' ' git update-index --refresh && cd sub && - git apply -p2 ../patch.file && + git apply ../patch.file && if grep " " file1 then echo "Eh?" From 3e8a5db966c26a0a986161103d59683b909a6c78 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 21 Feb 2007 16:05:56 -0800 Subject: [PATCH 054/201] git-apply: guess correct -p value for non-git patches. This enhances the third point in the previous commit. When applying a non-git patch that begins like this: --- 2.6.orig/mm/slab.c +++ 2.6/mm/slab.c @@ -N,M +L,K @@@ ... and if you are in 'mm' subdirectory, we notice that -p2 is the right option to use to apply the patch in file slab.c in the current directory (i.e. mm/slab.c) The guess function also knows about this pattern, where you would need to use -p0 if applying from the top-level: --- mm/slab.c +++ mm/slab.c @@ -N,M +L,K @@@ ... Signed-off-by: Junio C Hamano --- builtin-apply.c | 59 +++++++++++++++++++++++++++++++++++++++-- t/t4119-apply-config.sh | 56 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 111 insertions(+), 4 deletions(-) diff --git a/builtin-apply.c b/builtin-apply.c index 12f00e38db..c7d4bdd474 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -28,6 +28,7 @@ static int newfd = -1; static int unidiff_zero; static int p_value = 1; +static int p_value_known; static int check_index; static int write_index; static int cached; @@ -312,11 +313,54 @@ static char *find_name(const char *line, char *def, int p_value, int terminate) return name; } +static int count_slashes(const char *cp) +{ + int cnt = 0; + char ch; + + while ((ch = *cp++)) + if (ch == '/') + cnt++; + return cnt; +} + +/* + * Given the string after "--- " or "+++ ", guess the appropriate + * p_value for the given patch. + */ +static int guess_p_value(const char *nameline) +{ + char *name, *cp; + int val = -1; + + if (is_dev_null(nameline)) + return -1; + name = find_name(nameline, NULL, 0, TERM_SPACE | TERM_TAB); + if (!name) + return -1; + cp = strchr(name, '/'); + if (!cp) + val = 0; + else if (prefix) { + /* + * Does it begin with "a/$our-prefix" and such? Then this is + * very likely to apply to our directory. + */ + if (!strncmp(name, prefix, prefix_length)) + val = count_slashes(prefix); + else { + cp++; + if (!strncmp(cp, prefix, prefix_length)) + val = count_slashes(prefix) + 1; + } + } + free(name); + return val; +} + /* * Get the name etc info from the --/+++ lines of a traditional patch header * - * NOTE! This hardcodes "-p1" behaviour in filename detection. - * * FIXME! The end-of-filename heuristics are kind of screwy. For existing * files, we can happily check the index for a match, but for creating a * new file we should try to match whatever "patch" does. I have no idea. @@ -327,6 +371,16 @@ static void parse_traditional_patch(const char *first, const char *second, struc first += 4; /* skip "--- " */ second += 4; /* skip "+++ " */ + if (!p_value_known) { + int p, q; + p = guess_p_value(first); + q = guess_p_value(second); + if (p < 0) p = q; + if (0 <= p && p == q) { + p_value = p; + p_value_known = 1; + } + } if (is_dev_null(first)) { patch->is_new = 1; patch->is_delete = 0; @@ -2656,6 +2710,7 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) } if (!strncmp(arg, "-p", 2)) { p_value = atoi(arg + 2); + p_value_known = 1; continue; } if (!strcmp(arg, "--no-add")) { diff --git a/t/t4119-apply-config.sh b/t/t4119-apply-config.sh index 32e0d7172e..55f46737c6 100755 --- a/t/t4119-apply-config.sh +++ b/t/t4119-apply-config.sh @@ -19,7 +19,7 @@ test_expect_success setup ' ' # Also handcraft GNU diff output; note this has trailing whitespace. -cat >gpatch.file <<\EOF +cat >gpatch.file <<\EOF && --- file1 2007-02-21 01:04:24.000000000 -0800 +++ file1+ 2007-02-21 01:07:44.000000000 -0800 @@ -1 +1 @@ @@ -27,6 +27,12 @@ cat >gpatch.file <<\EOF +B EOF +sed -e 's|file1|sub/&|' gpatch.file >gpatch-sub.file && +sed -e ' + /^--- /s|file1|a/sub/&| + /^+++ /s|file1|b/sub/&| +' gpatch.file >gpatch-ab-sub.file && + test_expect_success 'apply --whitespace=strip' ' rm -f sub/file1 && @@ -124,7 +130,53 @@ test_expect_success 'same in subdir but with traditional patch input' ' git update-index --refresh && cd sub && - git apply -p0 ../gpatch.file && + git apply ../gpatch.file && + if grep " " file1 + then + echo "Eh?" + false + elif grep B file1 + then + echo Happy + else + echo "Huh?" + false + fi +' + +test_expect_success 'same but with traditional patch input of depth 1' ' + + cd "$D" && + git config apply.whitespace strip && + rm -f sub/file1 && + cp saved sub/file1 && + git update-index --refresh && + + cd sub && + git apply ../gpatch-sub.file && + if grep " " file1 + then + echo "Eh?" + false + elif grep B file1 + then + echo Happy + else + echo "Huh?" + false + fi +' + +test_expect_success 'same but with traditional patch input of depth 2' ' + + cd "$D" && + git config apply.whitespace strip && + rm -f sub/file1 && + cp saved sub/file1 && + git update-index --refresh && + + cd sub && + git apply ../gpatch-ab-sub.file && if grep " " file1 then echo "Eh?" From fe6e0eecb03379e6acb742f77b0b5f589a7b7422 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 21 Feb 2007 16:18:45 -0800 Subject: [PATCH 055/201] t4119: test autocomputing -p for traditional diff input. Signed-off-by: Junio C Hamano --- t/t4119-apply-config.sh | 121 +++++++++++++++------------------------- 1 file changed, 45 insertions(+), 76 deletions(-) diff --git a/t/t4119-apply-config.sh b/t/t4119-apply-config.sh index 55f46737c6..620a9207bf 100755 --- a/t/t4119-apply-config.sh +++ b/t/t4119-apply-config.sh @@ -33,6 +33,20 @@ sed -e ' /^+++ /s|file1|b/sub/&| ' gpatch.file >gpatch-ab-sub.file && +check_result () { + if grep " " "$1" + then + echo "Eh?" + false + elif grep B "$1" + then + echo Happy + else + echo "Huh?" + false + fi +} + test_expect_success 'apply --whitespace=strip' ' rm -f sub/file1 && @@ -40,17 +54,7 @@ test_expect_success 'apply --whitespace=strip' ' git update-index --refresh && git apply --whitespace=strip patch.file && - if grep " " sub/file1 - then - echo "Eh?" - false - elif grep B sub/file1 - then - echo Happy - else - echo "Huh?" - false - fi + check_result sub/file1 ' test_expect_success 'apply --whitespace=strip from config' ' @@ -61,16 +65,7 @@ test_expect_success 'apply --whitespace=strip from config' ' git config apply.whitespace strip && git apply patch.file && - if grep " " sub/file1 - then - echo "Eh?" - false - elif grep B sub/file1 - then - echo Happy - else - echo Happy - fi + check_result sub/file1 ' D=`pwd` @@ -85,17 +80,7 @@ test_expect_success 'apply --whitespace=strip in subdir' ' cd sub && git apply --whitespace=strip ../patch.file && - if grep " " file1 - then - echo "Eh?" - false - elif grep B file1 - then - echo Happy - else - echo "Huh?" - false - fi + check_result file1 ' test_expect_success 'apply --whitespace=strip from config in subdir' ' @@ -108,17 +93,7 @@ test_expect_success 'apply --whitespace=strip from config in subdir' ' cd sub && git apply ../patch.file && - if grep " " file1 - then - echo "Eh?" - false - elif grep B file1 - then - echo Happy - else - echo "Huh?" - false - fi + check_result file1 ' test_expect_success 'same in subdir but with traditional patch input' ' @@ -131,17 +106,7 @@ test_expect_success 'same in subdir but with traditional patch input' ' cd sub && git apply ../gpatch.file && - if grep " " file1 - then - echo "Eh?" - false - elif grep B file1 - then - echo Happy - else - echo "Huh?" - false - fi + check_result file1 ' test_expect_success 'same but with traditional patch input of depth 1' ' @@ -154,17 +119,7 @@ test_expect_success 'same but with traditional patch input of depth 1' ' cd sub && git apply ../gpatch-sub.file && - if grep " " file1 - then - echo "Eh?" - false - elif grep B file1 - then - echo Happy - else - echo "Huh?" - false - fi + check_result file1 ' test_expect_success 'same but with traditional patch input of depth 2' ' @@ -177,17 +132,31 @@ test_expect_success 'same but with traditional patch input of depth 2' ' cd sub && git apply ../gpatch-ab-sub.file && - if grep " " file1 - then - echo "Eh?" - false - elif grep B file1 - then - echo Happy - else - echo "Huh?" - false - fi + check_result file1 +' + +test_expect_success 'same but with traditional patch input of depth 1' ' + + cd "$D" && + git config apply.whitespace strip && + rm -f sub/file1 && + cp saved sub/file1 && + git update-index --refresh && + + git apply -p0 gpatch-sub.file && + check_result sub/file1 +' + +test_expect_success 'same but with traditional patch input of depth 2' ' + + cd "$D" && + git config apply.whitespace strip && + rm -f sub/file1 && + cp saved sub/file1 && + git update-index --refresh && + + git apply gpatch-ab-sub.file && + check_result sub/file1 ' test_done From e4a15f40bcd9e5679345d3cc74db3162310a7c83 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 21 Feb 2007 17:59:08 -0500 Subject: [PATCH 056/201] Document the new core.bare configuration option. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/config.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Documentation/config.txt b/Documentation/config.txt index 38655350f2..4a22a00b71 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -142,6 +142,18 @@ core.preferSymlinkRefs:: This is sometimes needed to work with old scripts that expect HEAD to be a symbolic link. +core.bare:: + If true this repository is assumed to be 'bare' and has no + working directory associated with it. If this is the case a + number of commands that require a working directory will be + disabled, such as gitlink:git-add[1] or gitlink:git-merge[1]. ++ +This setting is automatically guessed by gitlink:git-clone[1] or +gitlink:git-init[1] when the repository was created. By default a +repository that ends in "/.git" is assumed to be not bare (bare = +false), while all other repositories are assumed to be bare (bare += true). + core.logAllRefUpdates:: Updates to a ref is logged to the file "$GIT_DIR/logs/", by appending the new and old From 4917d2a66e8fa8c40ea6082c0fd8b58492c9444e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 21 Feb 2007 23:49:51 -0500 Subject: [PATCH 057/201] Include git-gui credits file in dist. The Makefile for the git-gui subproject will fail to execute if run outside of a git.git directory, such as when building from a .tar.gz or .tar.bz2. This is because it is looking for the credits file, which was created but omitted from the tarball by the toplevel Makefile. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f85fb7c197..64d29f7c18 100644 --- a/Makefile +++ b/Makefile @@ -895,7 +895,8 @@ dist: git.spec git-archive $(TAR) rf $(GIT_TARNAME).tar \ $(GIT_TARNAME)/git.spec \ $(GIT_TARNAME)/version \ - $(GIT_TARNAME)/git-gui/version + $(GIT_TARNAME)/git-gui/version \ + $(GIT_TARNAME)/git-gui/credits @rm -rf $(GIT_TARNAME) gzip -f -9 $(GIT_TARNAME).tar From aeabfa072564d9261fa80b1314949bdfd9b78632 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 22 Feb 2007 20:11:21 +0100 Subject: [PATCH 058/201] apply: make --verbose a little more useful When a patch fails, I automatically add '-v' to the command line to see what fails. This patch makes -v a synonym to --verbose, and actually tells the user which text was not found. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin-apply.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/builtin-apply.c b/builtin-apply.c index 3fefdacd94..17f4ad595e 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -1655,6 +1655,8 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, i /* Ignore it, we already handled it */ break; default: + if (apply_verbosely) + error("invalid start of line: '%c'", first); return -1; } patch += len; @@ -1752,6 +1754,9 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, i } } + if (offset && apply_verbosely) + error("while searching for:\n%.*s", oldsize, oldlines); + free(old); free(new); return offset; @@ -2692,7 +2697,7 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) apply = apply_with_reject = apply_verbosely = 1; continue; } - if (!strcmp(arg, "--verbose")) { + if (!strcmp(arg, "-v") || !strcmp(arg, "--verbose")) { apply_verbosely = 1; continue; } From 755b99d81539461645088085ea033a3b36152da5 Mon Sep 17 00:00:00 2001 From: Fredrik Kuivinen Date: Thu, 22 Feb 2007 21:28:12 +0100 Subject: [PATCH 059/201] Fix 'git commit -a' in a newly initialized repository With current git: $ git init $ git commit -a cp: cannot stat `.git/index': No such file or directory Output a nice error message instead. Signed-off-by: Fredrik Kuivinen Signed-off-by: Junio C Hamano --- git-commit.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/git-commit.sh b/git-commit.sh index ec506d956f..476f4f18db 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -318,6 +318,10 @@ esac case "$all,$also" in t,) + if test ! -f "$THIS_INDEX" + then + die 'nothing to commit (use "git add file1 file2" to include for commit)' + fi save_index && ( cd_to_toplevel && From 75b62b489af7b62a5518c3f199d2a2776205e088 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 23 Feb 2007 05:20:32 +0100 Subject: [PATCH 060/201] git-diff: fix combined diff The code forgets that typecast binds tighter than addition, in other words: (cast *)array + i === ((cast *)array) + i Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin-diff.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/builtin-diff.c b/builtin-diff.c index a6590205e8..c387ebb16c 100644 --- a/builtin-diff.c +++ b/builtin-diff.c @@ -192,7 +192,8 @@ static int builtin_diff_combined(struct rev_info *revs, parent = xmalloc(ents * sizeof(*parent)); /* Again, the revs are all reverse */ for (i = 0; i < ents; i++) - hashcpy((unsigned char*)parent + i, ent[ents - 1 - i].item->sha1); + hashcpy((unsigned char *)(parent + i), + ent[ents - 1 - i].item->sha1); diff_tree_combined(parent[0], parent + 1, ents - 1, revs->dense_combined_merges, revs); return 0; From 8565d2d853d85f246faa9bcde91aba3415a24d54 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 15 Feb 2007 11:43:56 +0100 Subject: [PATCH 061/201] Make tests independent of global config files This was done by setting $HOME to somewhere bogus. A better method is to reuse $GIT_CONFIG, which was invented for ignoring the global config file explicitely. Technically, setting GIT_CONFIG=.git/config could be wrong, but it passes all the tests, and we can keep the tests that way. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- t/test-lib.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/t/test-lib.sh b/t/test-lib.sh index 37822fc13d..a403fe042b 100755 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -255,8 +255,8 @@ test_done () { PATH=$(pwd)/..:$PATH GIT_EXEC_PATH=$(pwd)/.. GIT_TEMPLATE_DIR=$(pwd)/../templates/blt -HOME=$(pwd)/trash -export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR HOME +GIT_CONFIG=.git/config +export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git export GITPERLLIB From d976acfd89a7ea539cdaf3fc806308d426b8007a Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 4 Jan 2007 00:45:03 -0800 Subject: [PATCH 062/201] git-svn: move authentication prompts into their own namespace I'm going to be reorganizing some more code. Signed-off-by: Eric Wong --- git-svn.perl | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index d792a62d7c..cbe4ed2050 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -69,7 +69,7 @@ $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit, $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m, $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive, - $_username, $_config_dir, $_no_auth_cache, + $_config_dir, $_pager, $_color, $_prefix); my (@_branch_from, %tree_map, %users, %rusers, %equiv); my ($_svn_can_do_switch); @@ -83,9 +83,9 @@ 'repack:i' => \$_repack, 'no-metadata' => \$_no_metadata, 'quiet|q' => \$_q, - 'username=s' => \$_username, + 'username=s' => \$Git::SVN::Prompt::_username, 'config-dir=s' => \$_config_dir, - 'no-auth-cache' => \$_no_auth_cache, + 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache, 'ignore-nodate' => \$_ignore_nodate, 'repack-flags|repack-args|repack-opts=s' => \$_repack_flags); @@ -131,9 +131,9 @@ 'Initialize multiple trees (like git-svnimport)', { %multi_opts, %init_opts, 'revision|r=i' => \$_revision, - 'username=s' => \$_username, + 'username=s' => \$Git::SVN::Prompt::_username, 'config-dir=s' => \$_config_dir, - 'no-auth-cache' => \$_no_auth_cache, + 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache, 'prefix=s' => \$_prefix, } ], 'multi-fetch' => [ \&multi_fetch, @@ -1912,7 +1912,13 @@ sub show_commit_normal { } } -sub _simple_prompt { +package Git::SVN::Prompt; +use strict; +use warnings; +require SVN::Core; +use vars qw/$_no_auth_cache $_username/; + +sub simple { my ($cred, $realm, $default_username, $may_save, $pool) = @_; $may_save = undef if $_no_auth_cache; $default_username = $_username if defined $_username; @@ -1923,7 +1929,7 @@ sub _simple_prompt { } $cred->username($default_username); } else { - _username_prompt($cred, $realm, $may_save, $pool); + username($cred, $realm, $may_save, $pool); } $cred->password(_read_password("Password for '" . $cred->username . "': ", $realm)); @@ -1931,7 +1937,7 @@ sub _simple_prompt { $SVN::_Core::SVN_NO_ERROR; } -sub _ssl_server_trust_prompt { +sub ssl_server_trust { my ($cred, $realm, $failures, $cert_info, $may_save, $pool) = @_; $may_save = undef if $_no_auth_cache; print STDERR "Error validating server certificate for '$realm':\n"; @@ -1980,7 +1986,7 @@ sub _ssl_server_trust_prompt { $SVN::_Core::SVN_NO_ERROR; } -sub _ssl_client_cert_prompt { +sub ssl_client_cert { my ($cred, $realm, $may_save, $pool) = @_; $may_save = undef if $_no_auth_cache; print STDERR "Client certificate filename: "; @@ -1991,7 +1997,7 @@ sub _ssl_client_cert_prompt { $SVN::_Core::SVN_NO_ERROR; } -sub _ssl_client_cert_pw_prompt { +sub ssl_client_cert_pw { my ($cred, $realm, $may_save, $pool) = @_; $may_save = undef if $_no_auth_cache; $cred->password(_read_password("Password: ", $realm)); @@ -1999,7 +2005,7 @@ sub _ssl_client_cert_pw_prompt { $SVN::_Core::SVN_NO_ERROR; } -sub _username_prompt { +sub username { my ($cred, $realm, $may_save, $pool) = @_; $may_save = undef if $_no_auth_cache; if (defined $realm && length $realm) { @@ -2035,6 +2041,8 @@ sub _read_password { $password; } +package main; + sub libsvn_connect { my ($url) = @_; SVN::_Core::svn_config_ensure($_config_dir, undef); @@ -2042,16 +2050,16 @@ sub libsvn_connect { SVN::Client::get_simple_provider(), SVN::Client::get_ssl_server_trust_file_provider(), SVN::Client::get_simple_prompt_provider( - \&_simple_prompt, 2), + \&Git::SVN::Prompt::simple, 2), SVN::Client::get_ssl_client_cert_prompt_provider( - \&_ssl_client_cert_prompt, 2), + \&Git::SVN::Prompt::ssl_client_cert, 2), SVN::Client::get_ssl_client_cert_pw_prompt_provider( - \&_ssl_client_cert_pw_prompt, 2), + \&Git::SVN::Prompt::ssl_client_cert_pw, 2), SVN::Client::get_username_provider(), SVN::Client::get_ssl_server_trust_prompt_provider( - \&_ssl_server_trust_prompt), + \&Git::SVN::Prompt::ssl_server_trust), SVN::Client::get_username_prompt_provider( - \&_username_prompt, 2), + \&Git::SVN::username, 2), ]); my $config = SVN::Core::config_get_config($_config_dir); my $ra = SVN::Ra->new(url => $url, auth => $baton, From 4a87db0e12df48408501610605bd7cde81c6d20e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 4 Jan 2007 01:38:18 -0800 Subject: [PATCH 063/201] git-svn: cleanup: move process_rm around (it's only used in one function now) Signed-off-by: Eric Wong --- git-svn.perl | 44 +++++++++++++++++++------------------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index cbe4ed2050..fcef05c2ee 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -40,7 +40,6 @@ } push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor'; push @SVN::Git::Fetcher::ISA, 'SVN::Delta::Editor'; -*SVN::Git::Fetcher::process_rm = *process_rm; use Carp qw/croak/; use IO::File qw//; use File::Basename qw/dirname basename/; @@ -2181,28 +2180,6 @@ sub libsvn_log_entry { revprops => $rp } } -sub process_rm { - my ($gui, $last_commit, $f, $q) = @_; - # remove entire directories. - if (command('ls-tree',$last_commit,'--',$f) =~ /^040000 tree/) { - my ($ls, $ctx) = command_output_pipe(qw/ls-tree - -r --name-only -z/, - $last_commit,'--',$f); - local $/ = "\0"; - while (<$ls>) { - print $gui '0 ',0 x 40,"\t",$_ or croak $!; - print "\tD\t$_\n" unless $q; - } - print "\tD\t$f/\n" unless $q; - command_close_pipe($ls, $ctx); - return $SVN::Node::dir; - } else { - print $gui '0 ',0 x 40,"\t",$f,"\0" or croak $!; - print "\tD\t$f\n" unless $q; - return $SVN::Node::file; - } -} - sub libsvn_fetch { my ($last_commit, $paths, $rev, $author, $date, $msg) = @_; my $pool = SVN::Pool->new; @@ -2634,8 +2611,25 @@ sub open_directory { sub delete_entry { my ($self, $path, $rev, $pb) = @_; - my $t = process_rm($self->{gui}, $self->{c}, $path, $self->{q}); - $self->{empty}->{$path} = 0 if $t == $SVN::Node::dir; + my $gui = $self->{gui}; + + # remove entire directories. + if (command('ls-tree', $self->{c}, '--', $path) =~ /^040000 tree/) { + my ($ls, $ctx) = command_output_pipe(qw/ls-tree + -r --name-only -z/, + $self->{c}, '--', $path); + local $/ = "\0"; + while (<$ls>) { + print $gui '0 ',0 x 40,"\t",$_ or croak $!; + print "\tD\t$_\n" unless $self->{q}; + } + print "\tD\t$path/\n" unless $self->{q}; + command_close_pipe($ls, $ctx); + $self->{empty}->{$path} = 0 + } else { + print $gui '0 ',0 x 40,"\t",$path,"\0" or croak $!; + print "\tD\t$path\n" unless $self->{q}; + } undef; } From d81bf827192f0af6b1cca64d2cdbaac9b5ca2020 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 10 Jan 2007 01:22:38 -0800 Subject: [PATCH 064/201] git-svn: cleanup: put SVN workarounds into their own namespace Force some svn_ra functions to use a temporary pool via wrapper This cleans up the code a bit by removing explicit instances of pool allocation and deallocation and providing wrapper functions that make use of temporary pools. I've also added an explicit pool usage when creating the commit editor for commit-diff where get_commit_editor can be called multiple times with the same pool previously. Signed-off-by: Eric Wong --- git-svn.perl | 322 +++++++++++++++++++++++++++------------------------ 1 file changed, 173 insertions(+), 149 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index fcef05c2ee..5a3a877709 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -4,7 +4,7 @@ use warnings; use strict; use vars qw/ $AUTHOR $VERSION - $SVN_URL $SVN_INFO $SVN_WC $SVN_UUID + $SVN_URL $GIT_SVN_INDEX $GIT_SVN $GIT_DIR $GIT_SVN_DIR $REVDB/; $AUTHOR = 'Eric Wong '; @@ -38,6 +38,7 @@ if ($SVN::Core::VERSION lt '1.1.0') { fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)\n"; } +push @Git::SVN::Ra::ISA, 'SVN::Ra'; push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor'; push @SVN::Git::Fetcher::ISA, 'SVN::Delta::Editor'; use Carp qw/croak/; @@ -63,16 +64,16 @@ my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit, $_find_copies_harder, $_l, $_cp_similarity, $_cp_remote, $_repack, $_repack_nr, $_repack_flags, $_q, - $_message, $_file, $_follow_parent, $_no_metadata, + $_message, $_file, $_no_metadata, $_template, $_shared, $_no_default_regex, $_no_graft_copy, $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit, $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m, $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive, - $_config_dir, $_pager, $_color, $_prefix); my (@_branch_from, %tree_map, %users, %rusers, %equiv); my ($_svn_can_do_switch); my @repo_path_split_cache; +use vars qw/$_follow_parent/; my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext, 'branch|b=s' => \@_branch_from, @@ -83,7 +84,7 @@ 'no-metadata' => \$_no_metadata, 'quiet|q' => \$_q, 'username=s' => \$Git::SVN::Prompt::_username, - 'config-dir=s' => \$_config_dir, + 'config-dir=s' => \$Git::SVN::Ra::config_dir, 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache, 'ignore-nodate' => \$_ignore_nodate, 'repack-flags|repack-args|repack-opts=s' => \$_repack_flags); @@ -131,7 +132,7 @@ { %multi_opts, %init_opts, 'revision|r=i' => \$_revision, 'username=s' => \$Git::SVN::Prompt::_username, - 'config-dir=s' => \$_config_dir, + 'config-dir=s' => \$Git::SVN::Ra::config_dir, 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache, 'prefix=s' => \$_prefix, } ], @@ -236,6 +237,7 @@ sub rebuild { my ($rev_list, $ctx) = command_output_pipe("rev-list", "refs/remotes/$GIT_SVN"); my $latest; + my $svn_uuid; while (<$rev_list>) { chomp; my $c = $_; @@ -251,7 +253,7 @@ sub rebuild { # if we merged or otherwise started elsewhere, this is # how we break out of it - next if (defined $SVN_UUID && ($uuid ne $SVN_UUID)); + next if (defined $svn_uuid && ($uuid ne $svn_uuid)); next if (defined $SVN_URL && defined $url && ($url ne $SVN_URL)); unless (defined $latest) { @@ -259,7 +261,7 @@ sub rebuild { croak "SVN repository location required: $url\n"; } $SVN_URL ||= $url; - $SVN_UUID ||= $uuid; + $svn_uuid ||= $uuid; setup_git_svn(); $latest = $rev; } @@ -310,7 +312,7 @@ sub fetch { sub fetch_lib { my (@parents) = @_; $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url"); - $SVN ||= libsvn_connect($SVN_URL); + $SVN ||= Git::SVN::Ra->new($SVN_URL); my ($last_rev, $last_commit) = svn_grab_base_rev(); my ($base, $head) = libsvn_parse_revision($last_rev); if ($base > $head) { @@ -322,7 +324,6 @@ sub fetch_lib { # after processing a revision and SVN stuff seems to leak my $inc = 1000; my ($min, $max) = ($base, $head < $base+$inc ? $head : $base+$inc); - read_uuid(); if (defined $last_commit) { unless (-e $GIT_SVN_INDEX) { command_noisy('read-tree', $last_commit); @@ -352,8 +353,7 @@ sub fetch_lib { # performance sucks with it enabled, so it's much # faster to fetch revision ranges instead of relying # on the limiter. - libsvn_get_log(libsvn_dup_ra($SVN), [''], - $min, $max, 0, 1, 1, + $SVN->dup->get_log([''], $min, $max, 0, 1, 1, sub { my $log_msg; if ($last_commit) { @@ -378,7 +378,7 @@ sub fetch_lib { $min = $max + 1; $max += $inc; $max = $head if ($max > $head); - $SVN = libsvn_connect($SVN_URL); + $SVN = Git::SVN::Ra->new($SVN_URL); } restore_index($index); return { revision => $last_rev, commit => $last_commit }; @@ -424,8 +424,6 @@ sub commit_lib { " current: $fetched->{revision}\n"; exit 1; } - read_uuid(); - my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : (); my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$"; my $repo; @@ -437,9 +435,10 @@ sub commit_lib { # can't track down... (it's probably in the SVN code) defined(my $pid = open my $fh, '-|') or croak $!; if (!$pid) { + my $pool = SVN::Pool->new; my $ed = SVN::Git::Editor->new( { r => $r_last, - ra => libsvn_dup_ra($SVN), + ra => $SVN->dup, c => $c, svn_path => $SVN->{svn_path}, }, @@ -451,8 +450,7 @@ sub commit_lib { $log_msg->{msg}, $r_last, $cmt_last) - }, - @lock) + }, $pool) ); my $mods = libsvn_checkout_tree($cmt_last, $c, $ed); if (@$mods == 0) { @@ -461,6 +459,7 @@ sub commit_lib { } else { $ed->close_edit; } + $pool->clear; exit 0; } my ($r_new, $cmt_new, $no); @@ -534,7 +533,7 @@ sub dcommit { sub show_ignore { $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url"); my $repo; - $SVN ||= libsvn_connect($SVN_URL); + $SVN ||= Git::SVN::Ra->new($SVN_URL); my $r = defined $_revision ? $_revision : $SVN->get_latest_revnum; libsvn_traverse_ignore(\*STDOUT, '', $r); } @@ -716,16 +715,16 @@ sub commit_diff { $_message ||= get_commit_message($tb, "$GIT_DIR/.svn-commit.tmp.$$")->{msg}; } - $SVN ||= libsvn_connect($SVN_URL); + $SVN ||= Git::SVN::Ra->new($SVN_URL); if ($r eq 'HEAD') { $r = $SVN->get_latest_revnum; } elsif ($r !~ /^\d+$/) { die "revision argument: $r not understood by git-svn\n"; } - my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : (); my $rev_committed; + my $pool = SVN::Pool->new; my $ed = SVN::Git::Editor->new({ r => $r, - ra => libsvn_dup_ra($SVN), + ra => $SVN->dup, c => $tb, svn_path => $SVN->{svn_path} }, @@ -733,7 +732,8 @@ sub commit_diff { sub { $rev_committed = $_[0]; print "Committed $_[0]\n"; - }, @lock) + }, + $pool) ); eval { my $mods = libsvn_checkout_tree($ta, $tb, $ed); @@ -744,6 +744,7 @@ sub commit_diff { $ed->close_edit; } }; + $pool->clear; fatal "$@\n" if $@; $_message = $_file = undef; return $rev_committed; @@ -1012,7 +1013,7 @@ sub graft_file_copy_lib { my $tree_paths = $l_map->{$u}; my $pfx = common_prefix([keys %$tree_paths]); my ($repo, $path) = repo_path_split($u.$pfx); - $SVN = libsvn_connect($repo); + $SVN = Git::SVN::Ra->new($repo); my ($base, $head) = libsvn_parse_revision(); my $inc = 1000; @@ -1020,14 +1021,11 @@ sub graft_file_copy_lib { my $eh = $SVN::Error::handler; $SVN::Error::handler = \&libsvn_skip_unknown_revs; while (1) { - my $pool = SVN::Pool->new; - libsvn_get_log(libsvn_dup_ra($SVN), [$path], - $min, $max, 0, 2, 1, + $SVN->dup->get_log([$path], $min, $max, 0, 2, 1, sub { libsvn_graft_file_copies($grafts, $tree_paths, $path, @_); - }, $pool); - $pool->clear; + }); last if ($max >= $head); $min = $max + 1; $max += $inc; @@ -1095,13 +1093,6 @@ sub graft_merge_msg { } } -sub read_uuid { - return if $SVN_UUID; - my $pool = SVN::Pool->new; - $SVN_UUID = $SVN->get_uuid($pool); - $pool->clear; -} - sub verify_ref { my ($ref) = @_; eval { command_oneline([ 'rev-parse', '--verify', $ref ], @@ -1119,7 +1110,7 @@ sub repo_path_split { return ($u, $full_url); } } - my $tmp = libsvn_connect($full_url); + my $tmp = Git::SVN::Ra->new($full_url); return ($tmp->{repos_root}, $tmp->{svn_path}); } @@ -1371,10 +1362,10 @@ sub git_commit { } next if $skip; my ($url_p, $r_p, $uuid_p) = cmt_metadata($p); - next if (($SVN_UUID eq $uuid_p) && + next if (($SVN->uuid eq $uuid_p) && ($log_msg->{revision} > $r_p)); next if (defined $url_p && defined $SVN_URL && - ($SVN_UUID eq $uuid_p) && + ($SVN->uuid eq $uuid_p) && ($url_p eq $SVN_URL)); push @tmp_parents, $p; } @@ -1394,8 +1385,8 @@ sub git_commit { or croak $!; print $msg_fh $log_msg->{msg} or croak $!; unless ($_no_metadata) { - print $msg_fh "\ngit-svn-id: $SVN_URL\@$log_msg->{revision}", - " $SVN_UUID\n" or croak $!; + print $msg_fh "\ngit-svn-id: $SVN_URL\@$log_msg->{revision} ", + $SVN->uuid,"\n" or croak $!; } $msg_fh->flush == 0 or croak $!; close $msg_fh or croak $!; @@ -1429,7 +1420,7 @@ sub set_commit_env { $author = '(no author)'; } my ($name,$email) = defined $users{$author} ? @{$users{$author}} - : ($author,"$author\@$SVN_UUID"); + : ($author,$author . '@' . $SVN->uuid); $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name; $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email; $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_msg->{date}; @@ -1589,7 +1580,6 @@ sub init_vars { $REVDB = "$GIT_SVN_DIR/.rev_db"; $GIT_SVN_INDEX = "$GIT_SVN_DIR/index"; $SVN_URL = undef; - $SVN_WC = "$GIT_SVN_DIR/tree"; %tree_map = (); } @@ -2042,60 +2032,6 @@ sub _read_password { package main; -sub libsvn_connect { - my ($url) = @_; - SVN::_Core::svn_config_ensure($_config_dir, undef); - my ($baton, $callbacks) = SVN::Core::auth_open_helper([ - SVN::Client::get_simple_provider(), - SVN::Client::get_ssl_server_trust_file_provider(), - SVN::Client::get_simple_prompt_provider( - \&Git::SVN::Prompt::simple, 2), - SVN::Client::get_ssl_client_cert_prompt_provider( - \&Git::SVN::Prompt::ssl_client_cert, 2), - SVN::Client::get_ssl_client_cert_pw_prompt_provider( - \&Git::SVN::Prompt::ssl_client_cert_pw, 2), - SVN::Client::get_username_provider(), - SVN::Client::get_ssl_server_trust_prompt_provider( - \&Git::SVN::Prompt::ssl_server_trust), - SVN::Client::get_username_prompt_provider( - \&Git::SVN::username, 2), - ]); - my $config = SVN::Core::config_get_config($_config_dir); - my $ra = SVN::Ra->new(url => $url, auth => $baton, - config => $config, - pool => SVN::Pool->new, - auth_provider_callbacks => $callbacks); - $ra->{svn_path} = $url; - $ra->{repos_root} = $ra->get_repos_root; - $ra->{svn_path} =~ s#^\Q$ra->{repos_root}\E/*##; - push @repo_path_split_cache, qr/^(\Q$ra->{repos_root}\E)/; - return $ra; -} - -sub libsvn_can_do_switch { - unless (defined $_svn_can_do_switch) { - my $pool = SVN::Pool->new; - my $rep = eval { - $SVN->do_switch(1, '', 0, $SVN->{url}, - SVN::Delta::Editor->new, $pool); - }; - if ($@) { - $_svn_can_do_switch = 0; - } else { - $rep->abort_report($pool); - $_svn_can_do_switch = 1; - } - $pool->clear; - } - $_svn_can_do_switch; -} - -sub libsvn_dup_ra { - my ($ra) = @_; - SVN::Ra->new(map { $_ => $ra->{$_} } qw/config url - auth auth_provider_callbacks repos_root svn_path/); -} - sub uri_encode { my ($f) = @_; $f =~ s#([^a-zA-Z0-9\*!\:_\./\-])#uc sprintf("%%%02x",ord($1))#eg; @@ -2165,14 +2101,12 @@ sub libsvn_log_entry { } # revprops (make this optional? it's an extra network trip...) - my $pool = SVN::Pool->new; - my $rp = $SVN->rev_proplist($rev, $pool); + my $rp = $SVN->rev_proplist($rev); foreach (sort keys %$rp) { next if /^svn:(?:author|date|log)$/; print $un " rev_prop: ", uri_encode($_), ' ', uri_encode($rp->{$_}), "\n"; } - $pool->clear; close $un or croak $!; { revision => $rev, date => "+0000 $Y-$m-$d $H:$M:$S", @@ -2182,15 +2116,9 @@ sub libsvn_log_entry { sub libsvn_fetch { my ($last_commit, $paths, $rev, $author, $date, $msg) = @_; - my $pool = SVN::Pool->new; my $ed = SVN::Git::Fetcher->new({ c => $last_commit, q => $_q }); - my $reporter = $SVN->do_update($rev, '', 1, $ed, $pool); - my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); my (undef, $last_rev, undef) = cmt_metadata($last_commit); - $reporter->set_path('', $last_rev, 0, @lock, $pool); - $reporter->finish_report($pool); - $pool->clear; - unless ($ed->{git_commit_ok}) { + unless ($SVN->gs_do_update($last_rev, $rev, '', 1, $ed)) { die "SVN connection failed somewhere...\n"; } libsvn_log_entry($rev, $author, $date, $msg, [$last_commit], $ed); @@ -2250,8 +2178,7 @@ sub libsvn_parse_revision { sub libsvn_traverse_ignore { my ($fh, $path, $r) = @_; $path =~ s#^/+##g; - my $pool = SVN::Pool->new; - my ($dirent, undef, $props) = $SVN->get_dir($path, $r, $pool); + my ($dirent, undef, $props) = $SVN->get_dir($path, $r); my $p = $path; $p =~ s#^\Q$SVN->{svn_path}\E/##; print $fh length $p ? "\n# $p\n" : "\n# /\n"; @@ -2270,7 +2197,6 @@ sub libsvn_traverse_ignore { next if $dirent->{$_}->kind != $SVN::Node::dir; libsvn_traverse_ignore($fh, "$path/$_", $r); } - $pool->clear; } sub revisions_eq { @@ -2278,10 +2204,7 @@ sub revisions_eq { return 1 if $r0 == $r1; my $nr = 0; # should be OK to use Pool here (r1 - r0) should be small - my $pool = SVN::Pool->new; - libsvn_get_log($SVN, [$path], $r0, $r1, - 0, 0, 1, sub {$nr++}, $pool); - $pool->clear; + $SVN->get_log([$path], $r0, $r1, 0, 0, 1, sub {$nr++}); return 0 if ($nr > 1); return 1; } @@ -2337,40 +2260,23 @@ sub libsvn_find_parent_branch { unlink $GIT_SVN_INDEX; print STDERR "Found branch parent: ($GIT_SVN) $parent\n"; command_noisy('read-tree', $parent); - unless (libsvn_can_do_switch()) { + unless ($SVN->can_do_switch) { return _libsvn_new_tree($paths, $rev, $author, $date, $msg, [$parent]); } # do_switch works with svn/trunk >= r22312, but that is not # included with SVN 1.4.2 (the latest version at the moment), # so we can't rely on it. - my $ra = libsvn_connect("$url/$branch_from"); + my $ra = Git::SVN::Ra->new("$url/$branch_from"); my $ed = SVN::Git::Fetcher->new({c => $parent, q => $_q }); - my $pool = SVN::Pool->new; - my $reporter = $ra->do_switch($rev, '', 1, $SVN->{url}, - $ed, $pool); - my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); - $reporter->set_path('', $r0, 0, @lock, $pool); - $reporter->finish_report($pool); - $pool->clear; - unless ($ed->{git_commit_ok}) { - die "SVN connection failed somewhere...\n"; - } + $ra->gs_do_switch($r0, $rev, '', 1, $SVN->{url}, $ed) or + die "SVN connection failed somewhere...\n"; return libsvn_log_entry($rev, $author, $date, $msg, [$parent]); } print STDERR "Nope, branch point not imported or unknown\n"; return undef; } -sub libsvn_get_log { - my ($ra, @args) = @_; - $args[4]-- if $args[4] && ! $_follow_parent; - if ($SVN::Core::VERSION le '1.2.0') { - splice(@args, 3, 1); - } - $ra->get_log(@args); -} - sub libsvn_new_tree { if (my $log_entry = libsvn_find_parent_branch(@_)) { return $log_entry; @@ -2381,14 +2287,8 @@ sub libsvn_new_tree { sub _libsvn_new_tree { my ($paths, $rev, $author, $date, $msg, $parents) = @_; - my $pool = SVN::Pool->new; my $ed = SVN::Git::Fetcher->new({q => $_q}); - my $reporter = $SVN->do_update($rev, '', 1, $ed, $pool); - my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); - $reporter->set_path('', $rev, 1, @lock, $pool); - $reporter->finish_report($pool); - $pool->clear; - unless ($ed->{git_commit_ok}) { + unless ($SVN->gs_do_update($rev, $rev, '', 1, $ed)) { die "SVN connection failed somewhere...\n"; } libsvn_log_entry($rev, $author, $date, $msg, $parents, $ed); @@ -2474,21 +2374,18 @@ sub libsvn_commit_cb { sub libsvn_ls_fullurl { my $fullurl = shift; - my $ra = libsvn_connect($fullurl); + my $ra = Git::SVN::Ra->new($fullurl); my @ret; - my $pool = SVN::Pool->new; my $r = defined $_revision ? $_revision : $ra->get_latest_revnum; - my ($dirent, undef, undef) = $ra->get_dir('', $r, $pool); + my ($dirent, undef, undef) = $ra->get_dir('', $r); foreach my $d (sort keys %$dirent) { if ($dirent->{$d}->kind == $SVN::Node::dir) { push @ret, "$d/"; # add '/' for compat with cli svn } } - $pool->clear; return @ret; } - sub libsvn_skip_unknown_revs { my $err = shift; my $errno = $err->apr_err(); @@ -2866,9 +2763,7 @@ sub rmdirs { sub open_or_add_dir { my ($self, $full_path, $baton) = @_; - my $p = SVN::Pool->new; - my $t = $self->{ra}->check_path($full_path, $self->{r}, $p); - $p->clear; + my $t = $self->{ra}->check_path($full_path, $self->{r}); if ($t == $SVN::Node::none) { return $self->add_directory($full_path, $baton, undef, -1, $self->{pool}); @@ -3023,6 +2918,135 @@ sub abort_edit { $self->{pool}->clear; } +package Git::SVN::Ra; +use vars qw/@ISA $config_dir/; +use strict; +use warnings; +my ($can_do_switch); + +BEGIN { + # enforce temporary pool usage for some simple functions + my $e; + foreach (qw/get_latest_revnum rev_proplist get_file + check_path get_dir get_uuid get_repos_root/) { + $e .= "sub $_ { + my \$self = shift; + my \$pool = SVN::Pool->new; + my \@ret = \$self->SUPER::$_(\@_,\$pool); + \$pool->clear; + wantarray ? \@ret : \$ret[0]; }\n"; + } + eval $e; +} + +sub new { + my ($class, $url) = @_; + SVN::_Core::svn_config_ensure($config_dir, undef); + my ($baton, $callbacks) = SVN::Core::auth_open_helper([ + SVN::Client::get_simple_provider(), + SVN::Client::get_ssl_server_trust_file_provider(), + SVN::Client::get_simple_prompt_provider( + \&Git::SVN::Prompt::simple, 2), + SVN::Client::get_ssl_client_cert_prompt_provider( + \&Git::SVN::Prompt::ssl_client_cert, 2), + SVN::Client::get_ssl_client_cert_pw_prompt_provider( + \&Git::SVN::Prompt::ssl_client_cert_pw, 2), + SVN::Client::get_username_provider(), + SVN::Client::get_ssl_server_trust_prompt_provider( + \&Git::SVN::Prompt::ssl_server_trust), + SVN::Client::get_username_prompt_provider( + \&Git::SVN::Prompt::username, 2), + ]); + my $config = SVN::Core::config_get_config($config_dir); + my $self = SVN::Ra->new(url => $url, auth => $baton, + config => $config, + pool => SVN::Pool->new, + auth_provider_callbacks => $callbacks); + $self->{svn_path} = $url; + $self->{repos_root} = $self->get_repos_root; + $self->{svn_path} =~ s#^\Q$self->{repos_root}\E/*##; + bless $self, $class; +} + +sub DESTROY { + my $self = shift; + $self->{pool}->clear if $self->{pool}; + $self->SUPER::DESTROY(@_); +} + +sub dup { + my ($self) = @_; + my $dup = SVN::Ra->new(pool => SVN::Pool->new, + map { $_ => $self->{$_} } qw/config url + auth auth_provider_callbacks repos_root svn_path/); + bless $dup, ref $self; +} + +sub get_log { + my ($self, @args) = @_; + my $pool = SVN::Pool->new; + $args[4]-- if $args[4] && ! $::_follow_parent; + splice(@args, 3, 1) if ($SVN::Core::VERSION le '1.2.0'); + my $ret = $self->SUPER::get_log(@args, $pool); + $pool->clear; + $ret; +} + +sub get_commit_editor { + my ($self, $msg, $cb, $pool) = @_; + my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : (); + $self->SUPER::get_commit_editor($msg, $cb, @lock, $pool); +} + +sub uuid { + my ($self) = @_; + $self->{uuid} ||= $self->get_uuid; +} + +sub gs_do_update { + my ($self, $rev_a, $rev_b, $path, $recurse, $editor) = @_; + my $pool = SVN::Pool->new; + my $reporter = $self->do_update($rev_b, $path, $recurse, + $editor, $pool); + my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); + my $new = ($rev_a == $rev_b); + $reporter->set_path($path, $rev_a, $new, @lock, $pool); + $reporter->finish_report($pool); + $pool->clear; + $editor->{git_commit_ok}; +} + +sub gs_do_switch { + my ($self, $rev_a, $rev_b, $path, $recurse, $url_b, $editor) = @_; + my $pool = SVN::Pool->new; + my $reporter = $self->do_switch($rev_b, $path, $recurse, + $url_b, $editor, $pool); + my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); + $reporter->set_path($path, $rev_a, 0, @lock, $pool); + $reporter->finish_report($pool); + $pool->clear; + $editor->{git_commit_ok}; +} + +sub can_do_switch { + my $self = shift; + unless (defined $can_do_switch) { + my $pool = SVN::Pool->new; + my $rep = eval { + $self->do_switch(1, '', 0, $self->{url}, + SVN::Delta::Editor->new, $pool); + }; + if ($@) { + $can_do_switch = 0; + } else { + $rep->abort_report($pool); + $can_do_switch = 1; + } + $pool->clear; + } + $can_do_switch; +} + __END__ Data structures: From 336f1714ae4211f97a33e2f672ec9c3479b4e5ba Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 11 Jan 2007 02:14:43 -0800 Subject: [PATCH 065/201] git-svn: cleanup: avoid re-use()ing Git.pm in sub-packages I will be using functions from Git.pm in more modules, so I want to avoid re-importing the long argument list everywhere it's used. Also removed an unused command-line switch (--no-ignore-externals) and some variables. Signed-off-by: Eric Wong --- git-svn.perl | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 5a3a877709..55d9412ec9 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -49,19 +49,28 @@ use POSIX qw/strftime/; use IPC::Open3; use Memoize; -use Git qw/command command_oneline command_noisy - command_output_pipe command_input_pipe command_close_pipe/; +use Git; memoize('revisions_eq'); memoize('cmt_metadata'); memoize('get_commit_time'); +BEGIN { + my $s; + foreach (qw/command command_oneline command_noisy command_output_pipe + command_input_pipe command_close_pipe/) { + $s .= "*SVN::Git::Editor::$_ = *SVN::Git::Fetcher::$_ = ". + "*$_ = *Git::$_; "; + } + eval $s; +} + my ($SVN); my $_optimize_commits = 1 unless $ENV{GIT_SVN_NO_OPTIMIZE_COMMITS}; my $sha1 = qr/[a-f\d]{40}/; my $sha1_short = qr/[a-f\d]{4,40}/; my $_esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/; -my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit, +my ($_revision,$_stdin,$_help,$_rmdir,$_edit, $_find_copies_harder, $_l, $_cp_similarity, $_cp_remote, $_repack, $_repack_nr, $_repack_flags, $_q, $_message, $_file, $_no_metadata, @@ -70,13 +79,11 @@ $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m, $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive, $_pager, $_color, $_prefix); -my (@_branch_from, %tree_map, %users, %rusers, %equiv); -my ($_svn_can_do_switch); +my (@_branch_from, %tree_map, %users, %rusers); my @repo_path_split_cache; use vars qw/$_follow_parent/; -my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext, - 'branch|b=s' => \@_branch_from, +my %fc_opts = ( 'branch|b=s' => \@_branch_from, 'follow-parent|follow' => \$_follow_parent, 'branch-all-refs|B' => \$_branch_all_refs, 'authors-file|A=s' => \$_authors, @@ -117,8 +124,7 @@ 'show-ignore' => [ \&show_ignore, "Show svn:ignore listings", { 'revision|r=i' => \$_revision } ], rebuild => [ \&rebuild, "Rebuild git-svn metadata (after git clone)", - { 'no-ignore-externals' => \$_no_ignore_ext, - 'copy-remote|remote=s' => \$_cp_remote, + { 'copy-remote|remote=s' => \$_cp_remote, 'upgrade' => \$_upgrade } ], 'graft-branches' => [ \&graft_branches, 'Detect merges/branches from already imported history', @@ -2476,8 +2482,6 @@ package SVN::Git::Fetcher; use warnings; use Carp qw/croak/; use IO::File qw//; -use Git qw/command command_oneline command_noisy - command_output_pipe command_input_pipe command_close_pipe/; # file baton members: path, mode_a, mode_b, pool, fh, blob, base sub new { @@ -2684,8 +2688,6 @@ package SVN::Git::Editor; use warnings; use Carp qw/croak/; use IO::File; -use Git qw/command command_oneline command_noisy - command_output_pipe command_input_pipe command_close_pipe/; sub new { my $class = shift; From 9b981fc6596e369e82bc744e12afd5e54f2514f0 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 11 Jan 2007 12:14:21 -0800 Subject: [PATCH 066/201] git-svn: add Git::SVN module (to avoid global variables) This should make it easier to improve multi-fetch and --follow-parent by avoiding global variables. Signed-off-by: Eric Wong --- git-svn.perl | 485 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 485 insertions(+) diff --git a/git-svn.perl b/git-svn.perl index 55d9412ec9..8abff90d97 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1907,6 +1907,491 @@ sub show_commit_normal { } } +package Git::SVN; +use strict; +use warnings; +use vars qw/$default/; +use Carp qw/croak/; +use File::Path qw/mkpath/; +use IPC::Open3; + +# properties that we do not log: +my %SKIP_PROP; +BEGIN { + %SKIP_PROP = map { $_ => 1 } qw/svn:wc:ra_dav:version-url + svn:special svn:executable + svn:entry:committed-rev + svn:entry:last-author + svn:entry:uuid + svn:entry:committed-date/; +} + +sub init { + my ($class, $id, $url) = @_; + my $self = _new($class, $id); + mkpath(["$self->{dir}/info"]); + if (defined $url) { + $url =~ s!/+$!!; # strip trailing slash + s_to_file($url, "$self->{dir}/info/url"); + } + $self->{url} = $url; + open my $fh, '>>', $self->{db_path} or croak $!; + close $fh or croak $!; + $self; +} + +sub new { + my ($class, $id) = @_; + my $self = _new($class, $id); + $self->{url} = file_to_s("$self->{dir}/info/url"); + $self; +} + +sub refname { "refs/remotes/$_[0]->{id}" } + +sub ra { + my ($self) = shift; + $self->{ra} ||= Git::SVN::Ra->new($self->{url}); +} + +sub copy_remote_ref { + my ($self) = @_; + my $origin = $::_cp_remote ? $::_cp_remote : 'origin'; + my $ref = $self->refname; + if (command('ls-remote', $origin, $ref)) { + command_noisy('fetch', $origin, "$ref:$ref"); + } elsif ($::_cp_remote && !$::_upgrade) { + die "Unable to find remote reference: $ref on $origin\n"; + } +} + +sub traverse_ignore { + my ($self, $fh, $path, $r) = @_; + $path =~ s#^/+##g; + my ($dirent, undef, $props) = $self->ra->get_dir($path, $r); + my $p = $path; + $p =~ s#^\Q$self->{ra}->{svn_path}\E/##; + print $fh length $p ? "\n# $p\n" : "\n# /\n"; + if (my $s = $props->{'svn:ignore'}) { + $s =~ s/[\r\n]+/\n/g; + chomp $s; + if (length $p == 0) { + $s =~ s#\n#\n/$p#g; + print $fh "/$s\n"; + } else { + $s =~ s#\n#\n/$p/#g; + print $fh "/$p/$s\n"; + } + } + foreach (sort keys %$dirent) { + next if $dirent->{$_}->kind != $SVN::Node::dir; + $self->traverse_ignore($fh, "$path/$_", $r); + } +} + +# returns the newest SVN revision number and newest commit SHA1 +sub last_rev_commit { + my ($self) = @_; + if (defined $self->{last_rev} && defined $self->{last_commit}) { + return ($self->{last_rev}, $self->{last_commit}); + } + my $c = verify_ref($self->refname.'^0'); + if (defined $c && length $c) { + my $rev = (cmt_metadata($c))[1]; + if (defined $rev) { + ($self->{last_rev}, $self->{last_commit}) = ($rev, $c); + return ($rev, $c); + } + } + my $offset = -41; # from tail + my $rl; + open my $fh, '<', $self->{db_path} or + croak "$self->{db_path} not readable: $!\n"; + seek $fh, $offset, 2; + $rl = readline $fh; + defined $rl or return (undef, undef); + chomp $rl; + while ($c ne $rl && tell $fh != 0) { + $offset -= 41; + seek $fh, $offset, 2; + $rl = readline $fh; + defined $rl or return (undef, undef); + chomp $rl; + } + my $rev = tell $fh; + croak $! if ($rev < 0); + $rev = ($rev - 41) / 41; + close $fh or croak $!; + ($self->{last_rev}, $self->{last_commit}) = ($rev, $c); + return ($rev, $c); +} + +sub parse_revision { + my ($self, $base) = @_; + my $head = $self->ra->get_latest_revnum; + if (!defined $::_revision || $::_revision eq 'BASE:HEAD') { + return ($base + 1, $head) if (defined $base); + return (0, $head); + } + return ($1, $2) if ($::_revision =~ /^(\d+):(\d+)$/); + return ($::_revision, $::_revision) if ($::_revision =~ /^\d+$/); + if ($::_revision =~ /^BASE:(\d+)$/) { + return ($base + 1, $1) if (defined $base); + return (0, $head); + } + return ($1, $head) if ($::_revision =~ /^(\d+):HEAD$/); + die "revision argument: $::_revision not understood by git-svn\n", + "Try using the command-line svn client instead\n"; +} + +sub tmp_index_do { + my ($self, $sub) = @_; + my $old_index = $ENV{GIT_INDEX_FILE}; + $ENV{GIT_INDEX_FILE} = $self->{index}; + my @ret = &$sub; + if ($old_index) { + $ENV{GIT_INDEX_FILE} = $old_index; + } else { + delete $ENV{GIT_INDEX_FILE}; + } + wantarray ? @ret : $ret[0]; +} + +sub assert_index_clean { + my ($self, $treeish) = @_; + + $self->tmp_index_do(sub { + command_noisy('read-tree', $treeish) unless -e $self->{index}; + my $x = command_oneline('write-tree'); + my ($y) = (command(qw/cat-file commit/, $treeish) =~ + /^tree ($::sha1)/mo); + if ($y ne $x) { + unlink $self->{index} or croak $!; + command_noisy('read-tree', $treeish); + } + $x = command_oneline('write-tree'); + if ($y ne $x) { + ::fatal "trees ($treeish) $y != $x\n", + "Something is seriously wrong...\n"; + } + }); +} + +sub get_commit_parents { + my ($self, $log_msg, @parents) = @_; + my (%seen, @ret, @tmp); + # commit parents can be conditionally bound to a particular + # svn revision via: "svn_revno=commit_sha1", filter them out here: + foreach my $p (@parents) { + next unless defined $p; + if ($p =~ /^(\d+)=($::sha1_short)$/o) { + push @tmp, $2 if $1 == $log_msg->{revision}; + } else { + push @tmp, $p if $p =~ /^$::sha1_short$/o; + } + } + if (my $cur = verify_ref($self->refname.'^0')) { + push @tmp, $cur; + } + push @tmp, $_ foreach (@{$log_msg->{parents}}, @tmp); + while (my $p = shift @tmp) { + next if $seen{$p}; + $seen{$p} = 1; + push @ret, $p; + # MAXPARENT is defined to 16 in commit-tree.c: + last if @ret >= 16; + } + if (@tmp) { + die "r$log_msg->{revision}: No room for parents:\n\t", + join("\n\t", @tmp), "\n"; + } + @ret; +} + +sub check_upgrade_needed { + my ($self) = @_; + if (!-r $self->{db_path}) { + -d $self->{dir} or mkpath([$self->{dir}]); + open my $fh, '>>', $self->{db_path} or croak $!; + close $fh; + } + return unless verify_ref($self->{id}.'-HEAD^0'); + my $head = verify_ref($self->refname.'^0'); + if ($@ || !$head) { + fatal("Please run: $0 rebuild --upgrade\n"); + } +} + +sub do_git_commit { + my ($self, $log_msg, @parents) = @_; + if (my $c = $self->rev_db_get($log_msg->{revision})) { + croak "$log_msg->{revision} = $c already exists! ", + "Why are we refetching it?\n"; + } + my ($name, $email) = author_name_email($log_msg->{author}, $self->ra); + $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name; + $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email; + $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_msg->{date}; + + my $tree = $log_msg->{tree}; + if (!defined $tree) { + $tree = $self->tmp_index_do(sub { + command_oneline('write-tree') }); + } + die "Tree is not a valid sha1: $tree\n" if $tree !~ /^$::sha1$/o; + + my @exec = ('git-commit-tree', $tree); + foreach ($self->get_commit_parents($log_msg, @parents)) { + push @exec, '-p', $_; + } + defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec)) + or croak $!; + print $msg_fh $log_msg->{log} or croak $!; + print $msg_fh "\ngit-svn-id: $self->{ra}->{url}\@$log_msg->{revision}", + " ", $self->ra->uuid,"\n" or croak $!; + $msg_fh->flush == 0 or croak $!; + close $msg_fh or croak $!; + chomp(my $commit = do { local $/; <$out_fh> }); + close $out_fh or croak $!; + waitpid $pid, 0; + croak $? if $?; + if ($commit !~ /^$::sha1$/o) { + die "Failed to commit, invalid sha1: $commit\n"; + } + + command_noisy('update-ref',$self->refname, $commit); + $self->rev_db_set($log_msg->{revision}, $commit); + + $self->{last_rev} = $log_msg->{revision}; + $self->{last_commit} = $commit; + print "r$log_msg->{revision} = $commit\n"; + return $commit; +} + +sub do_fetch { + my ($self, $paths, $rev) = @_; #, $author, $date, $msg) = @_; + my $ed = SVN::Git::Fetcher->new($self); + my ($last_rev, @parents); + if ($self->{last_commit}) { + $last_rev = $self->{last_rev}; + $ed->{c} = $self->{last_commit}; + @parents = ($self->{last_commit}); + } else { + $last_rev = $rev; + } + unless ($self->ra->do_update($last_rev, $rev, '', 1, $ed)) { + die "SVN connection failed somewhere...\n"; + } + $self->make_log_entry($rev, \@parents, $ed); +} + +sub write_untracked { + my ($self, $rev, $fh, $untracked) = @_; + my $h; + print $fh "r$rev\n" or croak $!; + $h = $untracked->{empty}; + foreach (sort keys %$h) { + my $act = $h->{$_} ? '+empty_dir' : '-empty_dir'; + print $fh " $act: ", uri_encode($_), "\n" or croak $!; + warn "W: $act: $_\n"; + } + foreach my $t (qw/dir_prop file_prop/) { + $h = $untracked->{$t} or next; + foreach my $path (sort keys %$h) { + my $ppath = $path eq '' ? '.' : $path; + foreach my $prop (sort keys %{$h->{$path}}) { + next if $SKIP{$prop}; + my $v = $h->{$path}->{$prop}; + if (defined $v) { + print $fh " +$t: ", + uri_encode($ppath), ' ', + uri_encode($prop), ' ', + uri_encode($v), "\n" + or croak $!; + } else { + print $fh " -$t: ", + uri_encode($ppath), ' ', + uri_encode($prop), "\n" + or croak $!; + } + } + } + } + foreach my $t (qw/absent_file absent_directory/) { + $h = $untracked->{$t} or next; + foreach my $parent (sort keys %$h) { + foreach my $path (sort @{$h->{$parent}}) { + print $fh " $t: ", + uri_encode("$parent/$path"), "\n" + or croak $!; + warn "W: $t: $parent/$path ", + "Insufficient permissions?\n"; + } + } + } +} + +sub make_log_entry { + my ($self, $rev, $parents, $untracked) = @_; + my $rp = $self->ra->rev_proplist($rev); + my %log_entry = ( parents => $parents || [], revision => $rev, + revprops => $rp, log => ''); + open my $un, '>>', "$self->{dir}/unhandled.log" or croak $!; + $self->write_untracked($rev, $un, $untracked); + foreach (sort keys %$rp) { + my $v = $rp->{$_}; + if (/^svn:(author|date|log)$/) { + $log_entry{$1} = $v; + } else { + print $un " rev_prop: ", uri_encode($_), ' ', + uri_encode($v), "\n"; + } + } + close $un or croak $!; + $log_entry{date} = parse_svn_date($log_entry{date}); + $log_entry{author} = check_author($log_entry{author}); + $log_entry{log} .= "\n"; + \%log_entry; +} + +sub fetch { + my ($self, @parents) = @_; + my ($last_rev, $last_commit) = $self->last_rev_commit; + my ($base, $head) = $self->parse_revision($last_rev); + return if ($base > $head); + if (defined $last_commit) { + $self->assert_index_clean($last_commit); + } + my $inc = 1000; + my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc); + my $err_handler = $SVN::Error::handler; + $SVN::Error::handler = \&skip_unknown_revs; + while (1) { + my @revs; + $self->ra->get_log([''], $min, $max, 0, 1, 1, sub { + my ($paths, $rev, $author, $date, $msg) = @_; + push @revs, $rev }); + foreach (@revs) { + my $log_entry = $self->do_fetch(undef, $_); + $self->do_git_commit($log_entry, @parents); + } + last if $max >= $head; + $min = $max + 1; + $max += $inc; + $max = $head if ($max > $head); + } + $SVN::Error::handler = $err_handler; +} + +sub set_tree_cb { + my ($self, $log_entry, $tree, $rev, $date, $author) = @_; + # TODO: enable and test optimized commits: + if (0 && $rev == ($self->{last_rev} + 1)) { + $log_entry->{revision} = $rev; + $log_entry->{author} = $author; + $self->do_git_commit($log_entry, "$rev=$tree"); + } else { + $self->fetch("$rev=$tree"); + } +} + +sub set_tree { + my ($self, $tree) = (shift, shift); + my $log_entry = get_commit_entry($tree); + unless ($self->{last_rev}) { + fatal("Must have an existing revision to commit\n"); + } + my $pool = SVN::Pool->new; + my $ed = SVN::Git::Editor->new({ r => $self->{last_rev}, + ra => $self->ra->dup, + c => $tree, + svn_path => $self->ra->{svn_path} + }, + $self->ra->get_commit_editor( + $log_entry->{log}, sub { + $self->set_tree_cb($log_entry, + $tree, @_); + }), + $pool); + my $mods = $ed->apply_diff($self->{last_commit}, $tree); + if (@$mods == 0) { + print "No changes\nr$self->{last_rev} = $tree\n"; + } + $pool->clear; +} + +sub skip_unknown_revs { + my ($err) = @_; + my $errno = $err->apr_err(); + # Maybe the branch we're tracking didn't + # exist when the repo started, so it's + # not an error if it doesn't, just continue + # + # Wonderfully consistent library, eh? + # 160013 - svn:// and file:// + # 175002 - http(s):// + # 175007 - http(s):// (this repo required authorization, too...) + # More codes may be discovered later... + if ($errno == 175007 || $errno == 175002 || $errno == 160013) { + return; + } + croak "Error from SVN, ($errno): ", $err->expanded_message,"\n"; +} + +# rev_db: +# Tie::File seems to be prone to offset errors if revisions get sparse, +# it's not that fast, either. Tie::File is also not in Perl 5.6. So +# one of my favorite modules is out :< Next up would be one of the DBM +# modules, but I'm not sure which is most portable... So I'll just +# go with something that's plain-text, but still capable of +# being randomly accessed. So here's my ultra-simple fixed-width +# database. All records are 40 characters + "\n", so it's easy to seek +# to a revision: (41 * rev) is the byte offset. +# A record of 40 0s denotes an empty revision. +# And yes, it's still pretty fast (faster than Tie::File). + +sub rev_db_set { + my ($self, $rev, $commit) = @_; + length $commit == 40 or croak "arg3 must be a full SHA1 hexsum\n"; + open my $fh, '+<', $self->{db_path} or croak $!; + my $offset = $rev * 41; + # assume that append is the common case: + seek $fh, 0, 2 or croak $!; + my $pos = tell $fh; + if ($pos < $offset) { + print $fh (('0' x 40),"\n") x (($offset - $pos) / 41) + or croak $!; + } + seek $fh, $offset, 0 or croak $!; + print $fh $commit,"\n" or croak $!; + close $fh or croak $!; +} + +sub rev_db_get { + my ($self, $rev) = @_; + my $ret; + my $offset = $rev * 41; + open my $fh, '<', $self->{db_path} or croak $!; + if (seek $fh, $offset, 0) { + $ret = readline $fh; + if (defined $ret) { + chomp $ret; + $ret = undef if ($ret =~ /^0{40}$/); + } + } + close $fh or croak $!; + $ret; +} + +sub _new { + my ($class, $id) = @_; + $id ||= $Git::SVN::default; + my $dir = "$ENV{GIT_DIR}/svn/$id"; + bless { id => $id, dir => $dir, index => "$dir/index", + db_path => "$dir/.rev_db" }, $class; +} + + package Git::SVN::Prompt; use strict; use warnings; From d2866f9e1fbe24d9ac9e24501a9be07c3c189d34 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 11 Jan 2007 12:26:16 -0800 Subject: [PATCH 067/201] git-svn: convert 'init' to use Git::SVN While we're at it, fix up some bugs in Git::SVN. Signed-off-by: Eric Wong --- git-svn.perl | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 8abff90d97..5ff0c73048 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -111,7 +111,7 @@ BEGIN my %cmd = ( fetch => [ \&cmd_fetch, "Download new revisions from SVN", { 'revision|r=s' => \$_revision, %fc_opts } ], - init => [ \&init, "Initialize a repo for tracking" . + init => [ \&cmd_init, "Initialize a repo for tracking" . " (requires URL argument)", \%init_opts ], dcommit => [ \&dcommit, 'Commit several diffs to merge with upstream', @@ -278,27 +278,24 @@ sub rebuild { command_close_pipe($rev_list, $ctx); } -sub init { +sub cmd_init { my $url = shift or die "SVN repository location required " . "as a command-line argument\n"; - $url =~ s!/+$!!; # strip trailing slash - if (my $repo_path = shift) { unless (-d $repo_path) { mkpath([$repo_path]); } - $GIT_DIR = $ENV{GIT_DIR} = $repo_path . "/.git"; - init_vars(); + chdir $repo_path or croak $!; + $ENV{GIT_DIR} = $repo_path . "/.git"; } - $SVN_URL = $url; - unless (-d $GIT_DIR) { + unless (-d $ENV{GIT_DIR}) { my @init_db = ('init'); push @init_db, "--template=$_template" if defined $_template; push @init_db, "--shared" if defined $_shared; command_noisy(@init_db); } - setup_git_svn(); + Git::SVN->init(undef, $url); } sub cmd_fetch { @@ -596,7 +593,7 @@ sub multi_init { print "GIT_SVN_ID set to 'trunk' for ", "$trunk_url ($_trunk)\n"; } - init($trunk_url); + cmd_init($trunk_url); command_noisy('config', 'svn.trunk', $trunk_url); } } @@ -917,7 +914,7 @@ sub complete_url_ls_init { init_vars(); unless (-d $GIT_SVN_DIR) { print "init $u => $id\n"; - init($u); + cmd_init($u); } } exit 0; @@ -1582,6 +1579,7 @@ sub find_rev_before { sub init_vars { $GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn'; + $Git::SVN::default = $GIT_SVN; $GIT_SVN_DIR = "$GIT_DIR/svn/$GIT_SVN"; $REVDB = "$GIT_SVN_DIR/.rev_db"; $GIT_SVN_INDEX = "$GIT_SVN_DIR/index"; @@ -1932,7 +1930,7 @@ sub init { mkpath(["$self->{dir}/info"]); if (defined $url) { $url =~ s!/+$!!; # strip trailing slash - s_to_file($url, "$self->{dir}/info/url"); + ::s_to_file($url, "$self->{dir}/info/url"); } $self->{url} = $url; open my $fh, '>>', $self->{db_path} or croak $!; @@ -1943,7 +1941,7 @@ sub init { sub new { my ($class, $id) = @_; my $self = _new($class, $id); - $self->{url} = file_to_s("$self->{dir}/info/url"); + $self->{url} = ::file_to_s("$self->{dir}/info/url"); $self; } @@ -1995,9 +1993,9 @@ sub last_rev_commit { if (defined $self->{last_rev} && defined $self->{last_commit}) { return ($self->{last_rev}, $self->{last_commit}); } - my $c = verify_ref($self->refname.'^0'); + my $c = ::verify_ref($self->refname.'^0'); if (defined $c && length $c) { - my $rev = (cmt_metadata($c))[1]; + my $rev = (::cmt_metadata($c))[1]; if (defined $rev) { ($self->{last_rev}, $self->{last_commit}) = ($rev, $c); return ($rev, $c); @@ -2090,7 +2088,7 @@ sub get_commit_parents { push @tmp, $p if $p =~ /^$::sha1_short$/o; } } - if (my $cur = verify_ref($self->refname.'^0')) { + if (my $cur = ::verify_ref($self->refname.'^0')) { push @tmp, $cur; } push @tmp, $_ foreach (@{$log_msg->{parents}}, @tmp); @@ -2115,10 +2113,10 @@ sub check_upgrade_needed { open my $fh, '>>', $self->{db_path} or croak $!; close $fh; } - return unless verify_ref($self->{id}.'-HEAD^0'); - my $head = verify_ref($self->refname.'^0'); + return unless ::verify_ref($self->{id}.'-HEAD^0'); + my $head = ::verify_ref($self->refname.'^0'); if ($@ || !$head) { - fatal("Please run: $0 rebuild --upgrade\n"); + ::fatal("Please run: $0 rebuild --upgrade\n"); } } @@ -2128,7 +2126,7 @@ sub do_git_commit { croak "$log_msg->{revision} = $c already exists! ", "Why are we refetching it?\n"; } - my ($name, $email) = author_name_email($log_msg->{author}, $self->ra); + my ($name, $email) = ::author_name_email($log_msg->{author}, $self->ra); $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name; $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email; $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_msg->{date}; From 8164b6525ef6d95d4b656a8f4226f1758a611989 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 11 Jan 2007 15:35:55 -0800 Subject: [PATCH 068/201] git-svn: convert multi-init over to using Git::SVN Signed-off-by: Eric Wong --- git-svn.perl | 72 ++++++++++++++++++++++------------------------------ 1 file changed, 31 insertions(+), 41 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 5ff0c73048..72f73ea623 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -133,7 +133,7 @@ BEGIN 'branch-all-refs|B' => \$_branch_all_refs, 'no-default-regex' => \$_no_default_regex, 'no-graft-copy' => \$_no_graft_copy } ], - 'multi-init' => [ \&multi_init, + 'multi-init' => [ \&cmd_multi_init, 'Initialize multiple trees (like git-svnimport)', { %multi_opts, %init_opts, 'revision|r=i' => \$_revision, @@ -278,6 +278,15 @@ sub rebuild { command_close_pipe($rev_list, $ctx); } +sub do_git_init_db { + unless (-d $ENV{GIT_DIR}) { + my @init_db = ('init'); + push @init_db, "--template=$_template" if defined $_template; + push @init_db, "--shared" if defined $_shared; + command_noisy(@init_db); + } +} + sub cmd_init { my $url = shift or die "SVN repository location required " . "as a command-line argument\n"; @@ -288,13 +297,8 @@ sub cmd_init { chdir $repo_path or croak $!; $ENV{GIT_DIR} = $repo_path . "/.git"; } + do_git_init_db(); - unless (-d $ENV{GIT_DIR}) { - my @init_db = ('init'); - push @init_db, "--template=$_template" if defined $_template; - push @init_db, "--shared" if defined $_shared; - command_noisy(@init_db); - } Git::SVN->init(undef, $url); } @@ -575,29 +579,22 @@ sub graft_branches { unlink "$gr_file~$gr_sha1" if $gr_sha1; } -sub multi_init { +sub cmd_multi_init { my $url = shift; unless (defined $_trunk || defined $_branches || defined $_tags) { usage(1); } + do_git_init_db(); + $_prefix = '' unless defined $_prefix; if (defined $_trunk) { - my $trunk_url = complete_svn_url($url, $_trunk); - my $ch_id; - if ($GIT_SVN eq 'git-svn') { - $ch_id = 1; - $GIT_SVN = $ENV{GIT_SVN_ID} = 'trunk'; - } - init_vars(); - unless (-d $GIT_SVN_DIR) { - if ($ch_id) { - print "GIT_SVN_ID set to 'trunk' for ", - "$trunk_url ($_trunk)\n"; - } - cmd_init($trunk_url); + my $gs_trunk = eval { Git::SVN->new($_prefix . 'trunk') }; + unless ($gs_trunk) { + my $trunk_url = complete_svn_url($url, $_trunk); + $gs_trunk = Git::SVN->init($_prefix . 'trunk', + $trunk_url); command_noisy('config', 'svn.trunk', $trunk_url); } } - $_prefix = '' unless defined $_prefix; complete_url_ls_init($url, $_branches, '--branches/-b', $_prefix); complete_url_ls_init($url, $_tags, '--tags/-t', $_prefix . 'tags/'); } @@ -900,27 +897,20 @@ sub complete_url_ls_init { } my $full_url = complete_svn_url($url, $path); my @ls = libsvn_ls_fullurl($full_url); - defined(my $pid = fork) or croak $!; - if (!$pid) { - foreach my $u (map { "$full_url/$_" } (grep m!/$!, @ls)) { - $u =~ s#/+$##; - if ($u !~ m!\Q$full_url\E/(.+)$!) { - print STDERR "W: Unrecognized URL: $u\n"; - die "This should never happen\n"; - } - # don't try to init already existing refs - my $id = $pfx.$1; - $GIT_SVN = $ENV{GIT_SVN_ID} = $id; - init_vars(); - unless (-d $GIT_SVN_DIR) { - print "init $u => $id\n"; - cmd_init($u); - } + foreach my $u (map { "$full_url/$_" } (grep m!/$!, @ls)) { + $u =~ s#/+$##; + if ($u !~ m!\Q$full_url\E/(.+)$!) { + print STDERR "W: Unrecognized URL: $u\n"; + die "This should never happen\n"; + } + # don't try to init already existing refs + my $id = $pfx.$1; + my $gs = eval { Git::SVN->new($id) }; + unless ($gs) { + print "init $u => $id\n"; + Git::SVN->init($id, $u); } - exit 0; } - waitpid $pid, 0; - croak $? if $?; my ($n) = ($switch =~ /^--(\w+)/); command_noisy('config', "svn.$n", $full_url); } From e7db67e6f18495332c4d688d3291b05851526a6e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 11 Jan 2007 17:09:26 -0800 Subject: [PATCH 069/201] git-svn: make multi-init capable of reusing the Ra connection If a user specified a seperate URL and --tags/--branches as a sepearte URL, allow the Ra object (and therefore the connection) to be reused. We'll get rid of libsvn_ls_fullurl() since it was only used in one place. Signed-off-by: Eric Wong --- git-svn.perl | 50 ++++++++++++++----------------- t/t9103-git-svn-graft-branches.sh | 6 ++++ 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 72f73ea623..02786f1a6a 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -595,8 +595,9 @@ sub cmd_multi_init { command_noisy('config', 'svn.trunk', $trunk_url); } } - complete_url_ls_init($url, $_branches, '--branches/-b', $_prefix); - complete_url_ls_init($url, $_tags, '--tags/-t', $_prefix . 'tags/'); + my $ra = $url ? Git::SVN::Ra->new($url) : undef; + complete_url_ls_init($ra, $_branches, '--branches/-b', $_prefix); + complete_url_ls_init($ra, $_tags, '--tags/-t', $_prefix . 'tags/'); } sub multi_fetch { @@ -890,29 +891,38 @@ sub complete_svn_url { } sub complete_url_ls_init { - my ($url, $path, $switch, $pfx) = @_; + my ($ra, $path, $switch, $pfx) = @_; unless ($path) { print STDERR "W: $switch not specified\n"; return; } - my $full_url = complete_svn_url($url, $path); - my @ls = libsvn_ls_fullurl($full_url); - foreach my $u (map { "$full_url/$_" } (grep m!/$!, @ls)) { - $u =~ s#/+$##; - if ($u !~ m!\Q$full_url\E/(.+)$!) { - print STDERR "W: Unrecognized URL: $u\n"; - die "This should never happen\n"; + $path =~ s#/+$##; + if ($path =~ m#^[a-z\+]+://#) { + $ra = Git::SVN::Ra->new($path); + $path = ''; + } else { + $path =~ s#^/+##; + unless ($ra) { + fatal("E: '$path' is not a complete URL ", + "and a separate URL is not specified\n"); } - # don't try to init already existing refs - my $id = $pfx.$1; + } + my $r = defined $_revision ? $_revision : $ra->get_latest_revnum; + my ($dirent, undef, undef) = $ra->get_dir($path, $r); + my $url = $ra->{url} . (length $path ? "/$path" : ''); + foreach my $d (sort keys %$dirent) { + next if ($dirent->{$d}->kind != $SVN::Node::dir); + my $u = "$url/$d"; + my $id = "$pfx$d"; my $gs = eval { Git::SVN->new($id) }; + # don't try to init already existing refs unless ($gs) { print "init $u => $id\n"; Git::SVN->init($id, $u); } } my ($n) = ($switch =~ /^--(\w+)/); - command_noisy('config', "svn.$n", $full_url); + command_noisy('config', "svn.$n", $url); } sub common_prefix { @@ -2851,20 +2861,6 @@ sub libsvn_commit_cb { } } -sub libsvn_ls_fullurl { - my $fullurl = shift; - my $ra = Git::SVN::Ra->new($fullurl); - my @ret; - my $r = defined $_revision ? $_revision : $ra->get_latest_revnum; - my ($dirent, undef, undef) = $ra->get_dir('', $r); - foreach my $d (sort keys %$dirent) { - if ($dirent->{$d}->kind == $SVN::Node::dir) { - push @ret, "$d/"; # add '/' for compat with cli svn - } - } - return @ret; -} - sub libsvn_skip_unknown_revs { my $err = shift; my $errno = $err->apr_err(); diff --git a/t/t9103-git-svn-graft-branches.sh b/t/t9103-git-svn-graft-branches.sh index 183ae3b1c2..8d946d2aa5 100755 --- a/t/t9103-git-svn-graft-branches.sh +++ b/t/t9103-git-svn-graft-branches.sh @@ -26,6 +26,12 @@ test_expect_success 'initialize repo' " git-svn multi-fetch " +test_expect_success 'multi-init set .git/config correctly' " + test '$svnrepo/trunk' = '`git repo-config --get svn.trunk`' && + test '$svnrepo/branches' = '`git repo-config --get svn.branches`' && + test '$svnrepo/tags' = '`git repo-config --get svn.tags`' + " + r1=`git-rev-list remotes/trunk | tail -n1` r2=`git-rev-list remotes/tags/a | tail -n1` r3=`git-rev-list remotes/a | tail -n1` From ad2f90851e03294b5b30f735252001bfc1bd9de3 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 11 Jan 2007 17:55:50 -0800 Subject: [PATCH 070/201] git-svn: add a test for show-ignore Signed-off-by: Eric Wong --- t/t9101-git-svn-props.sh | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/t/t9101-git-svn-props.sh b/t/t9101-git-svn-props.sh index e8133d81cb..622ea1c0df 100755 --- a/t/t9101-git-svn-props.sh +++ b/t/t9101-git-svn-props.sh @@ -121,4 +121,30 @@ b_ne_cr="`git-hash-object ne_cr`" test_expect_success 'CRLF + $Id$' "test '$a_cr' = '$b_cr'" test_expect_success 'CRLF + $Id$ (no newline)' "test '$a_ne_cr' = '$b_ne_cr'" +cat > show-ignore.expect <<\EOF + +# / +/no-such-file* + +# deeply +/deeply/no-such-file* + +# deeply/nested +/deeply/nested/no-such-file* + +# deeply/nested/directory +/deeply/nested/directory/no-such-file* +EOF + +test_expect_success 'test show-ignore' " + cd test_wc && + mkdir -p deeply/nested/directory && + svn add deeply && + svn propset -R svn:ignore 'no-such-file*' . + svn commit -m 'propset svn:ignore' + cd .. && + git-svn show-ignore > show-ignore.got && + cmp show-ignore.expect show-ignore.got + " + test_done From 5969cbe13c7e65db6441632d58e7dee40795a980 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 11 Jan 2007 17:58:39 -0800 Subject: [PATCH 071/201] git-svn: convert show-ignore over to Git::SVN Signed-off-by: Eric Wong --- git-svn.perl | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 02786f1a6a..e0bccbcdc9 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -121,7 +121,7 @@ BEGIN %cmt_opts, %fc_opts } ], 'set-tree' => [ \&commit, "Set an SVN repository to a git tree-ish", { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ], - 'show-ignore' => [ \&show_ignore, "Show svn:ignore listings", + 'show-ignore' => [ \&cmd_show_ignore, "Show svn:ignore listings", { 'revision|r=i' => \$_revision } ], rebuild => [ \&rebuild, "Rebuild git-svn metadata (after git clone)", { 'copy-remote|remote=s' => \$_cp_remote, @@ -537,12 +537,10 @@ sub dcommit { command_noisy(@finish, $gs); } -sub show_ignore { - $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url"); - my $repo; - $SVN ||= Git::SVN::Ra->new($SVN_URL); - my $r = defined $_revision ? $_revision : $SVN->get_latest_revnum; - libsvn_traverse_ignore(\*STDOUT, '', $r); +sub cmd_show_ignore { + my $gs = Git::SVN->new; + my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum); + $gs->traverse_ignore(\*STDOUT, '', $r); } sub graft_branches { From f8c9d1d27f250a7fe13d4d33a1a94604ad355529 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 12 Jan 2007 02:35:20 -0800 Subject: [PATCH 072/201] git-svn: moved the 'log' command into its own namespace More cleanup to separate out functionality and make things nicer to hack on. While we're at it, centralize loading of the authors into one place and correctly handle '(no author)' cases in when showing logs after-the-fact; and not just at commit time. Signed-off-by: Eric Wong --- git-svn.perl | 652 +++++++++++++++++++++++++-------------------------- 1 file changed, 325 insertions(+), 327 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index e0bccbcdc9..3a4e41389b 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -6,7 +6,8 @@ use vars qw/ $AUTHOR $VERSION $SVN_URL $GIT_SVN_INDEX $GIT_SVN - $GIT_DIR $GIT_SVN_DIR $REVDB/; + $GIT_DIR $GIT_SVN_DIR $REVDB + $_follow_parent $sha1 $sha1_short/; $AUTHOR = 'Eric Wong '; $VERSION = '@@GIT_VERSION@@'; @@ -15,7 +16,7 @@ $ENV{GIT_DIR} = $GIT_DIR; my $LC_ALL = $ENV{LC_ALL}; -my $TZ = $ENV{TZ}; +$Git::SVN::Log::TZ = $ENV{TZ}; # make sure the svn binary gives consistent output between locales and TZs: $ENV{TZ} = 'UTC'; $ENV{LC_ALL} = 'C'; @@ -46,7 +47,6 @@ use File::Basename qw/dirname basename/; use File::Path qw/mkpath/; use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev pass_through/; -use POSIX qw/strftime/; use IPC::Open3; use Memoize; use Git; @@ -59,7 +59,7 @@ BEGIN foreach (qw/command command_oneline command_noisy command_output_pipe command_input_pipe command_close_pipe/) { $s .= "*SVN::Git::Editor::$_ = *SVN::Git::Fetcher::$_ = ". - "*$_ = *Git::$_; "; + "*Git::SVN::Log::$_ = *Git::SVN::$_ = *$_ = *Git::$_; "; } eval $s; } @@ -67,21 +67,18 @@ BEGIN my ($SVN); my $_optimize_commits = 1 unless $ENV{GIT_SVN_NO_OPTIMIZE_COMMITS}; -my $sha1 = qr/[a-f\d]{40}/; -my $sha1_short = qr/[a-f\d]{4,40}/; -my $_esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/; +$sha1 = qr/[a-f\d]{40}/; +$sha1_short = qr/[a-f\d]{4,40}/; my ($_revision,$_stdin,$_help,$_rmdir,$_edit, $_find_copies_harder, $_l, $_cp_similarity, $_cp_remote, $_repack, $_repack_nr, $_repack_flags, $_q, $_message, $_file, $_no_metadata, $_template, $_shared, $_no_default_regex, $_no_graft_copy, - $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit, $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m, - $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive, - $_pager, $_color, $_prefix); -my (@_branch_from, %tree_map, %users, %rusers); + $_merge, $_strategy, $_dry_run, + $_prefix); +my (@_branch_from, %tree_map, %users); my @repo_path_split_cache; -use vars qw/$_follow_parent/; my %fc_opts = ( 'branch|b=s' => \@_branch_from, 'follow-parent|follow' => \$_follow_parent, @@ -93,7 +90,6 @@ BEGIN 'username=s' => \$Git::SVN::Prompt::_username, 'config-dir=s' => \$Git::SVN::Ra::config_dir, 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache, - 'ignore-nodate' => \$_ignore_nodate, 'repack-flags|repack-args|repack-opts=s' => \$_repack_flags); my ($_trunk, $_tags, $_branches); @@ -145,17 +141,17 @@ BEGIN 'multi-fetch' => [ \&multi_fetch, 'Fetch multiple trees (like git-svnimport)', \%fc_opts ], - 'log' => [ \&show_log, 'Show commit logs', - { 'limit=i' => \$_limit, + 'log' => [ \&Git::SVN::Log::cmd_show_log, 'Show commit logs', + { 'limit=i' => \$Git::SVN::Log::limit, 'revision|r=s' => \$_revision, - 'verbose|v' => \$_verbose, - 'incremental' => \$_incremental, - 'oneline' => \$_oneline, - 'show-commit' => \$_show_commit, - 'non-recursive' => \$_non_recursive, + 'verbose|v' => \$Git::SVN::Log::verbose, + 'incremental' => \$Git::SVN::Log::incremental, + 'oneline' => \$Git::SVN::Log::oneline, + 'show-commit' => \$Git::SVN::Log::show_commit, + 'non-recursive' => \$Git::SVN::Log::non_recursive, 'authors-file|A=s' => \$_authors, - 'color' => \$_color, - 'pager=s' => \$_pager, + 'color' => \$Git::SVN::Log::color, + 'pager=s' => \$Git::SVN::Log::pager, } ], 'commit-diff' => [ \&commit_diff, 'Commit a diff between two trees', { 'message|m=s' => \$_message, @@ -607,81 +603,6 @@ sub multi_fetch { rec_fetch('', "$GIT_DIR/svn", @_); } -sub show_log { - my (@args) = @_; - my ($r_min, $r_max); - my $r_last = -1; # prevent dupes - rload_authors() if $_authors; - if (defined $TZ) { - $ENV{TZ} = $TZ; - } else { - delete $ENV{TZ}; - } - if (defined $_revision) { - if ($_revision =~ /^(\d+):(\d+)$/) { - ($r_min, $r_max) = ($1, $2); - } elsif ($_revision =~ /^\d+$/) { - $r_min = $r_max = $_revision; - } else { - print STDERR "-r$_revision is not supported, use ", - "standard \'git log\' arguments instead\n"; - exit 1; - } - } - - config_pager(); - @args = (git_svn_log_cmd($r_min, $r_max), @args); - my $log = command_output_pipe(@args); - run_pager(); - my (@k, $c, $d); - - while (<$log>) { - if (/^${_esc_color}commit ($sha1_short)/o) { - my $cmt = $1; - if ($c && cmt_showable($c) && $c->{r} != $r_last) { - $r_last = $c->{r}; - process_commit($c, $r_min, $r_max, \@k) or - goto out; - } - $d = undef; - $c = { c => $cmt }; - } elsif (/^${_esc_color}author (.+) (\d+) ([\-\+]?\d+)$/) { - get_author_info($c, $1, $2, $3); - } elsif (/^${_esc_color}(?:tree|parent|committer) /) { - # ignore - } elsif (/^${_esc_color}:\d{6} \d{6} $sha1_short/o) { - push @{$c->{raw}}, $_; - } elsif (/^${_esc_color}[ACRMDT]\t/) { - # we could add $SVN->{svn_path} here, but that requires - # remote access at the moment (repo_path_split)... - s#^(${_esc_color})([ACRMDT])\t#$1 $2 #; - push @{$c->{changed}}, $_; - } elsif (/^${_esc_color}diff /) { - $d = 1; - push @{$c->{diff}}, $_; - } elsif ($d) { - push @{$c->{diff}}, $_; - } elsif (/^${_esc_color} (git-svn-id:.+)$/) { - ($c->{url}, $c->{r}, undef) = extract_metadata($1); - } elsif (s/^${_esc_color} //) { - push @{$c->{l}}, $_; - } - } - if ($c && defined $c->{r} && $c->{r} != $r_last) { - $r_last = $c->{r}; - process_commit($c, $r_min, $r_max, \@k); - } - if (@k) { - my $swap = $r_max; - $r_max = $r_min; - $r_min = $swap; - process_commit($_, $r_min, $r_max) foreach reverse @k; - } -out: - close $log; - print '-' x72,"\n" unless $_incremental || $_oneline; -} - sub commit_diff_usage { print STDERR "Usage: $0 commit-diff []\n"; exit 1 @@ -751,90 +672,6 @@ sub commit_diff { ########################### utility functions ######################### -sub cmt_showable { - my ($c) = @_; - return 1 if defined $c->{r}; - if ($c->{l} && $c->{l}->[-1] eq "...\n" && - $c->{a_raw} =~ /\@([a-f\d\-]+)>$/) { - my @msg = command(qw/cat-file commit/, $c->{c}); - shift @msg while ($msg[0] ne "\n"); - shift @msg; - @{$c->{l}} = grep !/^git-svn-id: /, @msg; - - (undef, $c->{r}, undef) = extract_metadata( - (grep(/^git-svn-id: /, @msg))[-1]); - } - return defined $c->{r}; -} - -sub log_use_color { - return 1 if $_color; - my ($dc, $dcvar); - $dcvar = 'color.diff'; - $dc = `git-config --get $dcvar`; - if ($dc eq '') { - # nothing at all; fallback to "diff.color" - $dcvar = 'diff.color'; - $dc = `git-config --get $dcvar`; - } - chomp($dc); - if ($dc eq 'auto') { - my $pc; - $pc = `git-config --get color.pager`; - if ($pc eq '') { - # does not have it -- fallback to pager.color - $pc = `git-config --bool --get pager.color`; - } - else { - $pc = `git-config --bool --get color.pager`; - if ($?) { - $pc = 'false'; - } - } - chomp($pc); - if (-t *STDOUT || (defined $_pager && $pc eq 'true')) { - return ($ENV{TERM} && $ENV{TERM} ne 'dumb'); - } - return 0; - } - return 0 if $dc eq 'never'; - return 1 if $dc eq 'always'; - chomp($dc = `git-config --bool --get $dcvar`); - return ($dc eq 'true'); -} - -sub git_svn_log_cmd { - my ($r_min, $r_max) = @_; - my @cmd = (qw/log --abbrev-commit --pretty=raw - --default/, "refs/remotes/$GIT_SVN"); - push @cmd, '-r' unless $_non_recursive; - push @cmd, qw/--raw --name-status/ if $_verbose; - push @cmd, '--color' if log_use_color(); - return @cmd unless defined $r_max; - if ($r_max == $r_min) { - push @cmd, '--max-count=1'; - if (my $c = revdb_get($REVDB, $r_max)) { - push @cmd, $c; - } - } else { - my ($c_min, $c_max); - $c_max = revdb_get($REVDB, $r_max); - $c_min = revdb_get($REVDB, $r_min); - if (defined $c_min && defined $c_max) { - if ($r_max > $r_max) { - push @cmd, "$c_min..$c_max"; - } else { - push @cmd, "$c_max..$c_min"; - } - } elsif ($r_max > $r_min) { - push @cmd, $c_max; - } else { - push @cmd, $c_min; - } - } - return @cmd; -} - sub fetch_child_id { my $id = shift; print "Fetching $id\n"; @@ -1484,22 +1321,16 @@ sub load_all_refs { # ' = real-name ' mapping based on git-svnimport: sub load_authors { open my $authors, '<', $_authors or die "Can't open $_authors $!\n"; + my $log = $cmd eq 'log'; while (<$authors>) { chomp; next unless /^(\S+?|\(no author\))\s*=\s*(.+?)\s*<(.+)>\s*$/; my ($user, $name, $email) = ($1, $2, $3); - $users{$user} = [$name, $email]; - } - close $authors or croak $!; -} - -sub rload_authors { - open my $authors, '<', $_authors or die "Can't open $_authors $!\n"; - while (<$authors>) { - chomp; - next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/; - my ($user, $name, $email) = ($1, $2, $3); - $rusers{"$name <$email>"} = $user; + if ($log) { + $Git::SVN::Log::rusers{"$name <$email>"} = $user; + } else { + $users{$user} = [$name, $email]; + } } close $authors or croak $!; } @@ -1769,140 +1600,6 @@ sub tz_to_s_offset { return ($1 * 60) + ($tz * 3600); } -# adapted from pager.c -sub config_pager { - $_pager ||= $ENV{GIT_PAGER} || $ENV{PAGER}; - if (!defined $_pager) { - $_pager = 'less'; - } elsif (length $_pager == 0 || $_pager eq 'cat') { - $_pager = undef; - } -} - -sub run_pager { - return unless -t *STDOUT; - pipe my $rfd, my $wfd or return; - defined(my $pid = fork) or croak $!; - if (!$pid) { - open STDOUT, '>&', $wfd or croak $!; - return; - } - open STDIN, '<&', $rfd or croak $!; - $ENV{LESS} ||= 'FRSX'; - exec $_pager or croak "Can't run pager: $! ($_pager)\n"; -} - -sub get_author_info { - my ($dest, $author, $t, $tz) = @_; - $author =~ s/(?:^\s*|\s*$)//g; - $dest->{a_raw} = $author; - my $_a; - if ($_authors) { - $_a = $rusers{$author} || undef; - } - if (!$_a) { - ($_a) = ($author =~ /<([^>]+)\@[^>]+>$/); - } - $dest->{t} = $t; - $dest->{tz} = $tz; - $dest->{a} = $_a; - # Date::Parse isn't in the standard Perl distro :( - if ($tz =~ s/^\+//) { - $t += tz_to_s_offset($tz); - } elsif ($tz =~ s/^\-//) { - $t -= tz_to_s_offset($tz); - } - $dest->{t_utc} = $t; -} - -sub process_commit { - my ($c, $r_min, $r_max, $defer) = @_; - if (defined $r_min && defined $r_max) { - if ($r_min == $c->{r} && $r_min == $r_max) { - show_commit($c); - return 0; - } - return 1 if $r_min == $r_max; - if ($r_min < $r_max) { - # we need to reverse the print order - return 0 if (defined $_limit && --$_limit < 0); - push @$defer, $c; - return 1; - } - if ($r_min != $r_max) { - return 1 if ($r_min < $c->{r}); - return 1 if ($r_max > $c->{r}); - } - } - return 0 if (defined $_limit && --$_limit < 0); - show_commit($c); - return 1; -} - -sub show_commit { - my $c = shift; - if ($_oneline) { - my $x = "\n"; - if (my $l = $c->{l}) { - while ($l->[0] =~ /^\s*$/) { shift @$l } - $x = $l->[0]; - } - $_l_fmt ||= 'A' . length($c->{r}); - print 'r',pack($_l_fmt, $c->{r}),' | '; - print "$c->{c} | " if $_show_commit; - print $x; - } else { - show_commit_normal($c); - } -} - -sub show_commit_changed_paths { - my ($c) = @_; - return unless $c->{changed}; - print "Changed paths:\n", @{$c->{changed}}; -} - -sub show_commit_normal { - my ($c) = @_; - print '-' x72, "\nr$c->{r} | "; - print "$c->{c} | " if $_show_commit; - print "$c->{a} | ", strftime("%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)", - localtime($c->{t_utc})), ' | '; - my $nr_line = 0; - - if (my $l = $c->{l}) { - while ($l->[$#$l] eq "\n" && $#$l > 0 - && $l->[($#$l - 1)] eq "\n") { - pop @$l; - } - $nr_line = scalar @$l; - if (!$nr_line) { - print "1 line\n\n\n"; - } else { - if ($nr_line == 1) { - $nr_line = '1 line'; - } else { - $nr_line .= ' lines'; - } - print $nr_line, "\n"; - show_commit_changed_paths($c); - print "\n"; - print $_ foreach @$l; - } - } else { - print "1 line\n"; - show_commit_changed_paths($c); - print "\n"; - - } - foreach my $x (qw/raw diff/) { - if ($c->{$x}) { - print "\n"; - print $_ foreach @{$c->{$x}} - } - } -} - package Git::SVN; use strict; use warnings; @@ -3516,6 +3213,307 @@ sub can_do_switch { $can_do_switch; } +package Git::SVN::Log; +use strict; +use warnings; +use POSIX qw/strftime/; +use vars qw/$TZ $limit $color $pager $non_recursive $verbose $oneline + %rusers $show_commit $incremental/; +my $l_fmt; + +sub cmt_showable { + my ($c) = @_; + return 1 if defined $c->{r}; + if ($c->{l} && $c->{l}->[-1] eq "...\n" && + $c->{a_raw} =~ /\@([a-f\d\-]+)>$/) { + my @msg = command(qw/cat-file commit/, $c->{c}); + shift @msg while ($msg[0] ne "\n"); + shift @msg; + @{$c->{l}} = grep !/^git-svn-id: /, @msg; + + (undef, $c->{r}, undef) = ::extract_metadata( + (grep(/^git-svn-id: /, @msg))[-1]); + } + return defined $c->{r}; +} + +sub log_use_color { + return 1 if $color; + my ($dc, $dcvar); + $dcvar = 'color.diff'; + $dc = `git-config --get $dcvar`; + if ($dc eq '') { + # nothing at all; fallback to "diff.color" + $dcvar = 'diff.color'; + $dc = `git-config --get $dcvar`; + } + chomp($dc); + if ($dc eq 'auto') { + my $pc; + $pc = `git-config --get color.pager`; + if ($pc eq '') { + # does not have it -- fallback to pager.color + $pc = `git-config --bool --get pager.color`; + } + else { + $pc = `git-config --bool --get color.pager`; + if ($?) { + $pc = 'false'; + } + } + chomp($pc); + if (-t *STDOUT || (defined $pager && $pc eq 'true')) { + return ($ENV{TERM} && $ENV{TERM} ne 'dumb'); + } + return 0; + } + return 0 if $dc eq 'never'; + return 1 if $dc eq 'always'; + chomp($dc = `git-config --bool --get $dcvar`); + return ($dc eq 'true'); +} + +sub git_svn_log_cmd { + my ($r_min, $r_max) = @_; + my $gs = Git::SVN->_new; + my @cmd = (qw/log --abbrev-commit --pretty=raw --default/, + $gs->refname); + push @cmd, '-r' unless $non_recursive; + push @cmd, qw/--raw --name-status/ if $verbose; + push @cmd, '--color' if log_use_color(); + return @cmd unless defined $r_max; + if ($r_max == $r_min) { + push @cmd, '--max-count=1'; + if (my $c = $gs->rev_db_get($r_max)) { + push @cmd, $c; + } + } else { + my ($c_min, $c_max); + $c_max = $gs->rev_db_get($r_max); + $c_min = $gs->rev_db_get($r_min); + if (defined $c_min && defined $c_max) { + if ($r_max > $r_max) { + push @cmd, "$c_min..$c_max"; + } else { + push @cmd, "$c_max..$c_min"; + } + } elsif ($r_max > $r_min) { + push @cmd, $c_max; + } else { + push @cmd, $c_min; + } + } + return @cmd; +} + +# adapted from pager.c +sub config_pager { + $pager ||= $ENV{GIT_PAGER} || $ENV{PAGER}; + if (!defined $pager) { + $pager = 'less'; + } elsif (length $pager == 0 || $pager eq 'cat') { + $pager = undef; + } +} + +sub run_pager { + return unless -t *STDOUT; + pipe my $rfd, my $wfd or return; + defined(my $pid = fork) or ::fatal "Can't fork: $!\n"; + if (!$pid) { + open STDOUT, '>&', $wfd or + ::fatal "Can't redirect to stdout: $!\n"; + return; + } + open STDIN, '<&', $rfd or ::fatal "Can't redirect stdin: $!\n"; + $ENV{LESS} ||= 'FRSX'; + exec $pager or ::fatal "Can't run pager: $! ($pager)\n"; +} + +sub get_author_info { + my ($dest, $author, $t, $tz) = @_; + $author =~ s/(?:^\s*|\s*$)//g; + $dest->{a_raw} = $author; + my $au; + if ($_authors) { + $au = $rusers{$author} || undef; + } + if (!$au) { + ($au) = ($author =~ /<([^>]+)\@[^>]+>$/); + } + $dest->{t} = $t; + $dest->{tz} = $tz; + $dest->{a} = $au; + # Date::Parse isn't in the standard Perl distro :( + if ($tz =~ s/^\+//) { + $t += ::tz_to_s_offset($tz); + } elsif ($tz =~ s/^\-//) { + $t -= ::tz_to_s_offset($tz); + } + $dest->{t_utc} = $t; +} + +sub process_commit { + my ($c, $r_min, $r_max, $defer) = @_; + if (defined $r_min && defined $r_max) { + if ($r_min == $c->{r} && $r_min == $r_max) { + show_commit($c); + return 0; + } + return 1 if $r_min == $r_max; + if ($r_min < $r_max) { + # we need to reverse the print order + return 0 if (defined $limit && --$limit < 0); + push @$defer, $c; + return 1; + } + if ($r_min != $r_max) { + return 1 if ($r_min < $c->{r}); + return 1 if ($r_max > $c->{r}); + } + } + return 0 if (defined $limit && --$limit < 0); + show_commit($c); + return 1; +} + +sub show_commit { + my $c = shift; + if ($oneline) { + my $x = "\n"; + if (my $l = $c->{l}) { + while ($l->[0] =~ /^\s*$/) { shift @$l } + $x = $l->[0]; + } + $l_fmt ||= 'A' . length($c->{r}); + print 'r',pack($l_fmt, $c->{r}),' | '; + print "$c->{c} | " if $show_commit; + print $x; + } else { + show_commit_normal($c); + } +} + +sub show_commit_changed_paths { + my ($c) = @_; + return unless $c->{changed}; + print "Changed paths:\n", @{$c->{changed}}; +} + +sub show_commit_normal { + my ($c) = @_; + print '-' x72, "\nr$c->{r} | "; + print "$c->{c} | " if $show_commit; + print "$c->{a} | ", strftime("%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)", + localtime($c->{t_utc})), ' | '; + my $nr_line = 0; + + if (my $l = $c->{l}) { + while ($l->[$#$l] eq "\n" && $#$l > 0 + && $l->[($#$l - 1)] eq "\n") { + pop @$l; + } + $nr_line = scalar @$l; + if (!$nr_line) { + print "1 line\n\n\n"; + } else { + if ($nr_line == 1) { + $nr_line = '1 line'; + } else { + $nr_line .= ' lines'; + } + print $nr_line, "\n"; + show_commit_changed_paths($c); + print "\n"; + print $_ foreach @$l; + } + } else { + print "1 line\n"; + show_commit_changed_paths($c); + print "\n"; + + } + foreach my $x (qw/raw diff/) { + if ($c->{$x}) { + print "\n"; + print $_ foreach @{$c->{$x}} + } + } +} + +sub cmd_show_log { + my (@args) = @_; + my ($r_min, $r_max); + my $r_last = -1; # prevent dupes + if (defined $TZ) { + $ENV{TZ} = $TZ; + } else { + delete $ENV{TZ}; + } + if (defined $::_revision) { + if ($::_revision =~ /^(\d+):(\d+)$/) { + ($r_min, $r_max) = ($1, $2); + } elsif ($::_revision =~ /^\d+$/) { + $r_min = $r_max = $::_revision; + } else { + ::fatal "-r$::_revision is not supported, use ", + "standard \'git log\' arguments instead\n"; + } + } + + config_pager(); + @args = (git_svn_log_cmd($r_min, $r_max), @args); + my $log = command_output_pipe(@args); + run_pager(); + my (@k, $c, $d); + my $esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/; + while (<$log>) { + if (/^${esc_color}commit ($::sha1_short)/o) { + my $cmt = $1; + if ($c && cmt_showable($c) && $c->{r} != $r_last) { + $r_last = $c->{r}; + process_commit($c, $r_min, $r_max, \@k) or + goto out; + } + $d = undef; + $c = { c => $cmt }; + } elsif (/^${esc_color}author (.+) (\d+) ([\-\+]?\d+)$/o) { + get_author_info($c, $1, $2, $3); + } elsif (/^${esc_color}(?:tree|parent|committer) /o) { + # ignore + } elsif (/^${esc_color}:\d{6} \d{6} $::sha1_short/o) { + push @{$c->{raw}}, $_; + } elsif (/^${esc_color}[ACRMDT]\t/) { + # we could add $SVN->{svn_path} here, but that requires + # remote access at the moment (repo_path_split)... + s#^(${esc_color})([ACRMDT])\t#$1 $2 #o; + push @{$c->{changed}}, $_; + } elsif (/^${esc_color}diff /o) { + $d = 1; + push @{$c->{diff}}, $_; + } elsif ($d) { + push @{$c->{diff}}, $_; + } elsif (/^${esc_color} (git-svn-id:.+)$/o) { + ($c->{url}, $c->{r}, undef) = ::extract_metadata($1); + } elsif (s/^${esc_color} //o) { + push @{$c->{l}}, $_; + } + } + if ($c && defined $c->{r} && $c->{r} != $r_last) { + $r_last = $c->{r}; + process_commit($c, $r_min, $r_max, \@k); + } + if (@k) { + my $swap = $r_max; + $r_max = $r_min; + $r_min = $swap; + process_commit($_, $r_min, $r_max) foreach reverse @k; + } +out: + eval { command_close_pipe($log) }; + print '-' x72,"\n" unless $incremental || $oneline; +} + __END__ Data structures: From e7f023c81a03ccdd25ce4b4c7ed77f367c8f7edd Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 12 Jan 2007 02:49:01 -0800 Subject: [PATCH 073/201] git-svn: port the 'rebuild' command to use Git::SVN objects Also correctly shared some variables needed for Git::SVN::Log Signed-off-by: Eric Wong --- git-svn.perl | 71 +++++++++++++++++++++------------------------------- 1 file changed, 28 insertions(+), 43 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 3a4e41389b..3e4f5b73ca 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -7,7 +7,8 @@ $SVN_URL $GIT_SVN_INDEX $GIT_SVN $GIT_DIR $GIT_SVN_DIR $REVDB - $_follow_parent $sha1 $sha1_short/; + $_follow_parent $sha1 $sha1_short $_revision + $_cp_remote $_upgrade/; $AUTHOR = 'Eric Wong '; $VERSION = '@@GIT_VERSION@@'; @@ -69,8 +70,8 @@ BEGIN my $_optimize_commits = 1 unless $ENV{GIT_SVN_NO_OPTIMIZE_COMMITS}; $sha1 = qr/[a-f\d]{40}/; $sha1_short = qr/[a-f\d]{4,40}/; -my ($_revision,$_stdin,$_help,$_rmdir,$_edit, - $_find_copies_harder, $_l, $_cp_similarity, $_cp_remote, +my ($_stdin,$_help,$_rmdir,$_edit, + $_find_copies_harder, $_l, $_cp_similarity, $_repack, $_repack_nr, $_repack_flags, $_q, $_message, $_file, $_no_metadata, $_template, $_shared, $_no_default_regex, $_no_graft_copy, @@ -119,7 +120,7 @@ BEGIN { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ], 'show-ignore' => [ \&cmd_show_ignore, "Show svn:ignore listings", { 'revision|r=i' => \$_revision } ], - rebuild => [ \&rebuild, "Rebuild git-svn metadata (after git clone)", + rebuild => [ \&cmd_rebuild, "Rebuild git-svn metadata (after git clone)", { 'copy-remote|remote=s' => \$_cp_remote, 'upgrade' => \$_upgrade } ], 'graft-branches' => [ \&graft_branches, @@ -223,53 +224,48 @@ sub version { exit 0; } -sub rebuild { - if (!verify_ref("refs/remotes/$GIT_SVN^0")) { - copy_remote_ref(); +sub cmd_rebuild { + my $url = shift; + my $gs = $url ? Git::SVN->init(undef, $url) + : eval { Git::SVN->new }; + $gs ||= Git::SVN->_new; + if (!verify_ref($gs->refname.'^0')) { + $gs->copy_remote_ref; } - $SVN_URL = shift or undef; - my $newest_rev = 0; if ($_upgrade) { - command_noisy('update-ref',"refs/remotes/$GIT_SVN"," - $GIT_SVN-HEAD"); + command_noisy('update-ref',$gs->refname, $gs->{id}.'-HEAD'); } else { - check_upgrade_needed(); + $gs->check_upgrade_needed; } - my ($rev_list, $ctx) = command_output_pipe("rev-list", - "refs/remotes/$GIT_SVN"); + my ($rev_list, $ctx) = command_output_pipe("rev-list", $gs->refname); my $latest; my $svn_uuid; while (<$rev_list>) { chomp; my $c = $_; - croak "Non-SHA1: $c\n" unless $c =~ /^$sha1$/o; - my @commit = grep(/^git-svn-id: /, - command(qw/cat-file commit/, $c)); - next if (!@commit); # skip merges - my ($url, $rev, $uuid) = extract_metadata($commit[$#commit]); - if (!defined $rev || !$uuid) { - croak "Unable to extract revision or UUID from ", - "$c, $commit[$#commit]\n"; - } + fatal "Non-SHA1: $c\n" unless $c =~ /^$sha1$/o; + my ($url, $rev, $uuid) = cmt_metadata($c); + + # ignore merges (from set-tree) + next if (!defined $rev || !$uuid); # if we merged or otherwise started elsewhere, this is # how we break out of it - next if (defined $svn_uuid && ($uuid ne $svn_uuid)); - next if (defined $SVN_URL && defined $url && ($url ne $SVN_URL)); + if ((defined $svn_uuid && ($uuid ne $svn_uuid)) || + ($gs->{url} && $url && ($url ne $gs->{url}))) { + next; + } unless (defined $latest) { - if (!$SVN_URL && !$url) { - croak "SVN repository location required: $url\n"; + if (!$gs->{url} && !$url) { + fatal "SVN repository location required\n"; } - $SVN_URL ||= $url; - $svn_uuid ||= $uuid; - setup_git_svn(); + $gs = Git::SVN->init(undef, $url); $latest = $rev; } - revdb_set($REVDB, $rev, $c); + $gs->rev_db_set($rev, $c); print "r$rev = $c\n"; - $newest_rev = $rev if ($rev > $newest_rev); } command_close_pipe($rev_list, $ctx); } @@ -2617,17 +2613,6 @@ sub revdb_get { return $ret; } -sub copy_remote_ref { - my $origin = $_cp_remote ? $_cp_remote : 'origin'; - my $ref = "refs/remotes/$GIT_SVN"; - if (command('ls-remote', $origin, $ref)) { - command_noisy('fetch', $origin, "$ref:$ref"); - } elsif ($_cp_remote && !$_upgrade) { - die "Unable to find remote reference: ", - "refs/remotes/$GIT_SVN on $origin\n"; - } -} - { my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file. $SVN::Node::dir.$SVN::Node::unknown. From c843c464b83237fba65dc46a10133fda9f475cc5 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 12 Jan 2007 03:07:31 -0800 Subject: [PATCH 074/201] git-svn: do not let Git.pm warn if we prematurely close pipes This mainly quiets down warnings when running git svn log. Signed-off-by: Eric Wong --- git-svn.perl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-svn.perl b/git-svn.perl index 3e4f5b73ca..dd639a1b9a 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -3495,7 +3495,7 @@ sub cmd_show_log { process_commit($_, $r_min, $r_max) foreach reverse @k; } out: - eval { command_close_pipe($log) }; + close $log; print '-' x72,"\n" unless $incremental || $oneline; } From 44320b9e0e279cd1f549e259d00753a02c86c21b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 13 Jan 2007 22:35:53 -0800 Subject: [PATCH 075/201] git-svn: convert the 'commit-diff' command to Git::SVN Also, convert all usage of 'log_msg' to 'log_entry' for consistency's sake SVN::Git::Editor::apply_diff now drives the rest of the editor. Signed-off-by: Eric Wong --- git-svn.perl | 366 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 233 insertions(+), 133 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index dd639a1b9a..575d7936db 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -8,7 +8,8 @@ $GIT_SVN_INDEX $GIT_SVN $GIT_DIR $GIT_SVN_DIR $REVDB $_follow_parent $sha1 $sha1_short $_revision - $_cp_remote $_upgrade/; + $_cp_remote $_upgrade $_rmdir $_q $_cp_similarity + $_find_copies_harder $_l/; $AUTHOR = 'Eric Wong '; $VERSION = '@@GIT_VERSION@@'; @@ -70,9 +71,8 @@ BEGIN my $_optimize_commits = 1 unless $ENV{GIT_SVN_NO_OPTIMIZE_COMMITS}; $sha1 = qr/[a-f\d]{40}/; $sha1_short = qr/[a-f\d]{4,40}/; -my ($_stdin,$_help,$_rmdir,$_edit, - $_find_copies_harder, $_l, $_cp_similarity, - $_repack, $_repack_nr, $_repack_flags, $_q, +my ($_stdin, $_help, $_edit, + $_repack, $_repack_nr, $_repack_flags, $_message, $_file, $_no_metadata, $_template, $_shared, $_no_default_regex, $_no_graft_copy, $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m, @@ -154,7 +154,8 @@ BEGIN 'color' => \$Git::SVN::Log::color, 'pager=s' => \$Git::SVN::Log::pager, } ], - 'commit-diff' => [ \&commit_diff, 'Commit a diff between two trees', + 'commit-diff' => [ \&cmd_commit_diff, + 'Commit a diff between two trees', { 'message|m=s' => \$_message, 'file|F=s' => \$_file, 'revision|r=s' => \$_revision, @@ -354,18 +355,18 @@ sub fetch_lib { # on the limiter. $SVN->dup->get_log([''], $min, $max, 0, 1, 1, sub { - my $log_msg; + my $log_entry; if ($last_commit) { - $log_msg = libsvn_fetch( + $log_entry = libsvn_fetch( $last_commit, @_); $last_commit = git_commit( - $log_msg, + $log_entry, $last_commit, @parents); } else { - $log_msg = libsvn_new_tree(@_); + $log_entry = libsvn_new_tree(@_); $last_commit = git_commit( - $log_msg, @parents); + $log_entry, @parents); } }); exit 0; @@ -428,7 +429,7 @@ sub commit_lib { my $repo; set_svn_commit_env(); foreach my $c (@revs) { - my $log_msg = get_commit_message($c, $commit_msg); + my $log_entry = get_commit_entry($c, $commit_msg); # fork for each commit because there's a memory leak I # can't track down... (it's probably in the SVN code) @@ -438,25 +439,21 @@ sub commit_lib { my $ed = SVN::Git::Editor->new( { r => $r_last, ra => $SVN->dup, - c => $c, svn_path => $SVN->{svn_path}, }, $SVN->get_commit_editor( - $log_msg->{msg}, + $log_entry->{log}, sub { libsvn_commit_cb( @_, $c, - $log_msg->{msg}, + $log_entry->{log}, $r_last, $cmt_last) }, $pool) ); - my $mods = libsvn_checkout_tree($cmt_last, $c, $ed); + my $mods = $ed->apply_diff($cmt_last, $c); if (@$mods == 0) { print "No changes\nr$r_last = $cmt_last\n"; - $ed->abort_edit; - } else { - $ed->close_edit; } $pool->clear; exit 0; @@ -599,6 +596,55 @@ sub multi_fetch { rec_fetch('', "$GIT_DIR/svn", @_); } +# this command is special because it requires no metadata +sub cmd_commit_diff { + my ($ta, $tb, $url) = @_; + my $usage = "Usage: $0 commit-diff -r ". + " []\n"; + fatal($usage) if (!defined $ta || !defined $tb); + if (!defined $url) { + my $gs = eval { Git::SVN->new }; + if (!$gs) { + fatal("Needed URL or usable git-svn --id in ", + "the command-line\n", $usage); + } + $url = $gs->{url}; + } + unless (defined $_revision) { + fatal("-r|--revision is a required argument\n", $usage); + } + if (defined $_message && defined $_file) { + fatal("Both --message/-m and --file/-F specified ", + "for the commit message.\n", + "I have no idea what you mean\n"); + } + if (defined $_file) { + $_message = file_to_s($_file); + } else { + $_message ||= get_commit_entry($tb)->{log}; + } + my $ra ||= Git::SVN::Ra->new($url); + my $r = $_revision; + if ($r eq 'HEAD') { + $r = $ra->get_latest_revnum; + } elsif ($r !~ /^\d+$/) { + die "revision argument: $r not understood by git-svn\n"; + } + my $pool = SVN::Pool->new; + my %ed_opts = ( r => $r, + ra => $ra->dup, + svn_path => $ra->{svn_path} ); + my $ed = SVN::Git::Editor->new(\%ed_opts, + $ra->get_commit_editor($_message, + sub { print "Committed r$_[0]\n" }), + $pool); + my $mods = $ed->apply_diff($ta, $tb); + if (@$mods == 0) { + print "No changes\n$ta == $tb\n"; + } + $pool->clear; +} + sub commit_diff_usage { print STDERR "Usage: $0 commit-diff []\n"; exit 1 @@ -628,8 +674,8 @@ sub commit_diff { if (defined $_file) { $_message = file_to_s($_file); } else { - $_message ||= get_commit_message($tb, - "$GIT_DIR/.svn-commit.tmp.$$")->{msg}; + $_message ||= get_commit_entry($tb, + "$GIT_DIR/.svn-commit.tmp.$$")->{log}; } $SVN ||= Git::SVN::Ra->new($SVN_URL); if ($r eq 'HEAD') { @@ -641,7 +687,6 @@ sub commit_diff { my $pool = SVN::Pool->new; my $ed = SVN::Git::Editor->new({ r => $r, ra => $SVN->dup, - c => $tb, svn_path => $SVN->{svn_path} }, $SVN->get_commit_editor($_message, @@ -652,12 +697,9 @@ sub commit_diff { $pool) ); eval { - my $mods = libsvn_checkout_tree($ta, $tb, $ed); + my $mods = $ed->apply_diff($ta, $tb); if (@$mods == 0) { print "No changes\n$ta == $tb\n"; - $ed->abort_edit; - } else { - $ed->close_edit; } }; $pool->clear; @@ -963,7 +1005,7 @@ sub setup_git_svn { sub get_tree_from_treeish { my ($treeish) = @_; - croak "Not a sha1: $treeish\n" unless $treeish =~ /^$sha1$/o; + # $treeish can be a symbolic ref, too: my $type = command_oneline(qw/cat-file -t/, $treeish); my $expected; while ($type eq 'tag') { @@ -972,7 +1014,7 @@ sub get_tree_from_treeish { if ($type eq 'commit') { $expected = (grep /^tree /, command(qw/cat-file commit/, $treeish))[0]; - ($expected) = ($expected =~ /^tree ($sha1)$/); + ($expected) = ($expected =~ /^tree ($sha1)$/o); die "Unable to get tree from $treeish\n" unless $expected; } elsif ($type eq 'tree') { $expected = $treeish; @@ -1034,58 +1076,44 @@ sub get_diff { return \@mods; } -sub libsvn_checkout_tree { - my ($from, $treeish, $ed) = @_; - my $mods = get_diff($from, $treeish); - return $mods unless (scalar @$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) { - my $f = $m->{chg}; - if (defined $o{$f}) { - $ed->$f($m, $_q); - } else { - croak "Invalid change type: $f\n"; - } - } - $ed->rmdirs($_q) if $_rmdir; - return $mods; -} +sub get_commit_entry { + my ($treeish) = shift; + my %log_entry = ( log => '', tree => get_tree_from_treeish($treeish) ); + my $commit_editmsg = "$ENV{GIT_DIR}/COMMIT_EDITMSG"; + my $commit_msg = "$ENV{GIT_DIR}/COMMIT_MSG"; + open my $log_fh, '>', $commit_editmsg or croak $!; -sub get_commit_message { - my ($commit, $commit_msg) = (@_); - my %log_msg = ( msg => '' ); - open my $msg, '>', $commit_msg or croak $!; - - my $type = command_oneline(qw/cat-file -t/, $commit); + my $type = command_oneline(qw/cat-file -t/, $treeish); if ($type eq 'commit' || $type eq 'tag') { my ($msg_fh, $ctx) = command_output_pipe('cat-file', - $type, $commit); + $type, $treeish); my $in_msg = 0; while (<$msg_fh>) { if (!$in_msg) { $in_msg = 1 if (/^\s*$/); } elsif (/^git-svn-id: /) { - # skip this, we regenerate the correct one - # on re-fetch anyways + # skip this for now, we regenerate the + # correct one on re-fetch anyways + # TODO: set *:merge properties or like... } else { - print $msg $_ or croak $!; + print $log_fh $_ or croak $!; } } command_close_pipe($msg_fh, $ctx); } - close $msg or croak $!; + close $log_fh or croak $!; if ($_edit || ($type eq 'tree')) { my $editor = $ENV{VISUAL} || $ENV{EDITOR} || 'vi'; - system($editor, $commit_msg); + # TODO: strip out spaces, comments, like git-commit.sh + system($editor, $commit_editmsg); } - - # file_to_s removes all trailing newlines, so just use chomp() here: - open $msg, '<', $commit_msg or croak $!; - { local $/; chomp($log_msg{msg} = <$msg>); } - close $msg or croak $!; - - return \%log_msg; + rename $commit_editmsg, $commit_msg or croak $!; + open $log_fh, '<', $commit_msg or croak $!; + { local $/; chomp($log_entry{log} = <$log_fh>); } + close $log_fh or croak $!; + unlink $commit_msg; + \%log_entry; } sub set_svn_commit_env { @@ -1150,12 +1178,12 @@ sub assert_revision_unknown { } sub git_commit { - my ($log_msg, @parents) = @_; - assert_revision_unknown($log_msg->{revision}); + my ($log_entry, @parents) = @_; + assert_revision_unknown($log_entry->{revision}); map_tree_joins() if (@_branch_from && !%tree_map); my (@tmp_parents, @exec_parents, %seen_parent); - if (my $lparents = $log_msg->{parents}) { + if (my $lparents = $log_entry->{parents}) { @tmp_parents = @$lparents } # commit parents can be conditionally bound to a particular @@ -1163,14 +1191,14 @@ sub git_commit { foreach my $p (@parents) { next unless defined $p; if ($p =~ /^(\d+)=($sha1_short)$/o) { - if ($1 == $log_msg->{revision}) { + if ($1 == $log_entry->{revision}) { push @tmp_parents, $2; } } else { push @tmp_parents, $p if $p =~ /$sha1_short/o; } } - my $tree = $log_msg->{tree}; + my $tree = $log_entry->{tree}; if (!defined $tree) { my $index = set_index($GIT_SVN_INDEX); $tree = command_oneline('write-tree'); @@ -1197,7 +1225,7 @@ sub git_commit { next if $skip; my ($url_p, $r_p, $uuid_p) = cmt_metadata($p); next if (($SVN->uuid eq $uuid_p) && - ($log_msg->{revision} > $r_p)); + ($log_entry->{revision} > $r_p)); next if (defined $url_p && defined $SVN_URL && ($SVN->uuid eq $uuid_p) && ($url_p eq $SVN_URL)); @@ -1212,14 +1240,14 @@ sub git_commit { last if @exec_parents > 16; } - set_commit_env($log_msg); + set_commit_env($log_entry); my @exec = ('git-commit-tree', $tree); push @exec, '-p', $_ foreach @exec_parents; defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec)) or croak $!; - print $msg_fh $log_msg->{msg} or croak $!; + print $msg_fh $log_entry->{log} or croak $!; unless ($_no_metadata) { - print $msg_fh "\ngit-svn-id: $SVN_URL\@$log_msg->{revision} ", + print $msg_fh "\ngit-svn-id: $SVN_URL\@$log_entry->{revision} ", $SVN->uuid,"\n" or croak $!; } $msg_fh->flush == 0 or croak $!; @@ -1232,10 +1260,10 @@ sub git_commit { die "Failed to commit, invalid sha1: $commit\n"; } command_noisy('update-ref',"refs/remotes/$GIT_SVN",$commit); - revdb_set($REVDB, $log_msg->{revision}, $commit); + revdb_set($REVDB, $log_entry->{revision}, $commit); # this output is read via pipe, do not change: - print "r$log_msg->{revision} = $commit\n"; + print "r$log_entry->{revision} = $commit\n"; return $commit; } @@ -1248,8 +1276,8 @@ sub check_repack { } sub set_commit_env { - my ($log_msg) = @_; - my $author = $log_msg->{author}; + my ($log_entry) = @_; + my $author = $log_entry->{author}; if (!defined $author || length $author == 0) { $author = '(no author)'; } @@ -1257,7 +1285,7 @@ sub set_commit_env { : ($author,$author . '@' . $SVN->uuid); $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name; $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email; - $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_msg->{date}; + $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_entry->{date}; } sub check_upgrade_needed { @@ -1767,14 +1795,14 @@ sub assert_index_clean { } sub get_commit_parents { - my ($self, $log_msg, @parents) = @_; + my ($self, $log_entry, @parents) = @_; my (%seen, @ret, @tmp); # commit parents can be conditionally bound to a particular # svn revision via: "svn_revno=commit_sha1", filter them out here: foreach my $p (@parents) { next unless defined $p; if ($p =~ /^(\d+)=($::sha1_short)$/o) { - push @tmp, $2 if $1 == $log_msg->{revision}; + push @tmp, $2 if $1 == $log_entry->{revision}; } else { push @tmp, $p if $p =~ /^$::sha1_short$/o; } @@ -1782,7 +1810,7 @@ sub get_commit_parents { if (my $cur = ::verify_ref($self->refname.'^0')) { push @tmp, $cur; } - push @tmp, $_ foreach (@{$log_msg->{parents}}, @tmp); + push @tmp, $_ foreach (@{$log_entry->{parents}}, @tmp); while (my $p = shift @tmp) { next if $seen{$p}; $seen{$p} = 1; @@ -1791,7 +1819,7 @@ sub get_commit_parents { last if @ret >= 16; } if (@tmp) { - die "r$log_msg->{revision}: No room for parents:\n\t", + die "r$log_entry->{revision}: No room for parents:\n\t", join("\n\t", @tmp), "\n"; } @ret; @@ -1812,17 +1840,18 @@ sub check_upgrade_needed { } sub do_git_commit { - my ($self, $log_msg, @parents) = @_; - if (my $c = $self->rev_db_get($log_msg->{revision})) { - croak "$log_msg->{revision} = $c already exists! ", + my ($self, $log_entry, @parents) = @_; + if (my $c = $self->rev_db_get($log_entry->{revision})) { + croak "$log_entry->{revision} = $c already exists! ", "Why are we refetching it?\n"; } - my ($name, $email) = ::author_name_email($log_msg->{author}, $self->ra); + my ($name, $email) = ::author_name_email($log_entry->{author}, + $self->ra); $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name; $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email; - $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_msg->{date}; + $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_entry->{date}; - my $tree = $log_msg->{tree}; + my $tree = $log_entry->{tree}; if (!defined $tree) { $tree = $self->tmp_index_do(sub { command_oneline('write-tree') }); @@ -1830,14 +1859,15 @@ sub do_git_commit { die "Tree is not a valid sha1: $tree\n" if $tree !~ /^$::sha1$/o; my @exec = ('git-commit-tree', $tree); - foreach ($self->get_commit_parents($log_msg, @parents)) { + foreach ($self->get_commit_parents($log_entry, @parents)) { push @exec, '-p', $_; } defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec)) or croak $!; - print $msg_fh $log_msg->{log} or croak $!; - print $msg_fh "\ngit-svn-id: $self->{ra}->{url}\@$log_msg->{revision}", - " ", $self->ra->uuid,"\n" or croak $!; + print $msg_fh $log_entry->{log} or croak $!; + print $msg_fh "\ngit-svn-id: ", $self->ra->{url}, '@', + $log_entry->{revision}, ' ', + $self->ra->uuid, "\n" or croak $!; $msg_fh->flush == 0 or croak $!; close $msg_fh or croak $!; chomp(my $commit = do { local $/; <$out_fh> }); @@ -1849,16 +1879,16 @@ sub do_git_commit { } command_noisy('update-ref',$self->refname, $commit); - $self->rev_db_set($log_msg->{revision}, $commit); + $self->rev_db_set($log_entry->{revision}, $commit); - $self->{last_rev} = $log_msg->{revision}; + $self->{last_rev} = $log_entry->{revision}; $self->{last_commit} = $commit; - print "r$log_msg->{revision} = $commit\n"; + print "r$log_entry->{revision} = $commit\n"; return $commit; } sub do_fetch { - my ($self, $paths, $rev) = @_; #, $author, $date, $msg) = @_; + my ($self, $paths, $rev) = @_; #, $author, $date, $log) = @_; my $ed = SVN::Git::Fetcher->new($self); my ($last_rev, @parents); if ($self->{last_commit}) { @@ -1958,7 +1988,7 @@ sub fetch { while (1) { my @revs; $self->ra->get_log([''], $min, $max, 0, 1, 1, sub { - my ($paths, $rev, $author, $date, $msg) = @_; + my ($paths, $rev, $author, $date, $log) = @_; push @revs, $rev }); foreach (@revs) { my $log_entry = $self->do_fetch(undef, $_); @@ -1993,7 +2023,6 @@ sub set_tree { my $pool = SVN::Pool->new; my $ed = SVN::Git::Editor->new({ r => $self->{last_rev}, ra => $self->ra->dup, - c => $tree, svn_path => $self->ra->{svn_path} }, $self->ra->get_commit_editor( @@ -2226,7 +2255,7 @@ sub uri_decode { } sub libsvn_log_entry { - my ($rev, $author, $date, $msg, $parents, $untracked) = @_; + my ($rev, $author, $date, $log, $parents, $untracked) = @_; my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T (\d\d)\:(\d\d)\:(\d\d).\d+Z$/x) or die "Unable to parse date: $date\n"; @@ -2234,7 +2263,7 @@ sub libsvn_log_entry { defined $_authors && ! defined $users{$author}) { die "Author: $author not defined in $_authors file\n"; } - $msg = '' if ($rev == 0 && !defined $msg); + $log = '' if ($rev == 0 && !defined $log); open my $un, '>>', "$GIT_SVN_DIR/unhandled.log" or croak $!; my $h; @@ -2290,18 +2319,18 @@ sub libsvn_log_entry { close $un or croak $!; { revision => $rev, date => "+0000 $Y-$m-$d $H:$M:$S", - author => $author, msg => $msg."\n", parents => $parents || [], + author => $author, log => $log."\n", parents => $parents || [], revprops => $rp } } sub libsvn_fetch { - my ($last_commit, $paths, $rev, $author, $date, $msg) = @_; + my ($last_commit, $paths, $rev, $author, $date, $log) = @_; my $ed = SVN::Git::Fetcher->new({ c => $last_commit, q => $_q }); my (undef, $last_rev, undef) = cmt_metadata($last_commit); unless ($SVN->gs_do_update($last_rev, $rev, '', 1, $ed)) { die "SVN connection failed somewhere...\n"; } - libsvn_log_entry($rev, $author, $date, $msg, [$last_commit], $ed); + libsvn_log_entry($rev, $author, $date, $log, [$last_commit], $ed); } sub svn_grab_base_rev { @@ -2390,7 +2419,7 @@ sub revisions_eq { } sub libsvn_find_parent_branch { - my ($paths, $rev, $author, $date, $msg) = @_; + my ($paths, $rev, $author, $date, $log) = @_; my $svn_path = '/'.$SVN->{svn_path}; # look for a parent from another branch: @@ -2442,7 +2471,7 @@ sub libsvn_find_parent_branch { command_noisy('read-tree', $parent); unless ($SVN->can_do_switch) { return _libsvn_new_tree($paths, $rev, $author, $date, - $msg, [$parent]); + $log, [$parent]); } # do_switch works with svn/trunk >= r22312, but that is not # included with SVN 1.4.2 (the latest version at the moment), @@ -2451,7 +2480,7 @@ sub libsvn_find_parent_branch { my $ed = SVN::Git::Fetcher->new({c => $parent, q => $_q }); $ra->gs_do_switch($r0, $rev, '', 1, $SVN->{url}, $ed) or die "SVN connection failed somewhere...\n"; - return libsvn_log_entry($rev, $author, $date, $msg, [$parent]); + return libsvn_log_entry($rev, $author, $date, $log, [$parent]); } print STDERR "Nope, branch point not imported or unknown\n"; return undef; @@ -2461,17 +2490,17 @@ sub libsvn_new_tree { if (my $log_entry = libsvn_find_parent_branch(@_)) { return $log_entry; } - my ($paths, $rev, $author, $date, $msg) = @_; # $pool is last - _libsvn_new_tree($paths, $rev, $author, $date, $msg, []); + my ($paths, $rev, $author, $date, $log) = @_; # $pool is last + _libsvn_new_tree($paths, $rev, $author, $date, $log, []); } sub _libsvn_new_tree { - my ($paths, $rev, $author, $date, $msg, $parents) = @_; + my ($paths, $rev, $author, $date, $log, $parents) = @_; my $ed = SVN::Git::Fetcher->new({q => $_q}); unless ($SVN->gs_do_update($rev, $rev, '', 1, $ed)) { die "SVN connection failed somewhere...\n"; } - libsvn_log_entry($rev, $author, $date, $msg, $parents, $ed); + libsvn_log_entry($rev, $author, $date, $log, $parents, $ed); } sub find_graft_path_commit { @@ -2536,9 +2565,9 @@ sub restore_index { } sub libsvn_commit_cb { - my ($rev, $date, $committer, $c, $msg, $r_last, $cmt_last) = @_; + my ($rev, $date, $committer, $c, $log, $r_last, $cmt_last) = @_; if ($_optimize_commits && $rev == ($r_last + 1)) { - my $log = libsvn_log_entry($rev,$committer,$date,$msg); + my $log = libsvn_log_entry($rev,$committer,$date,$log); $log->{tree} = get_tree_from_treeish($c); my $cmt = git_commit($log, $cmt_last, $c); my @diff = command('diff-tree', $cmt, $c); @@ -2843,7 +2872,7 @@ sub new { my $git_svn = shift; my $self = SVN::Delta::Editor->new(@_); bless $self, $class; - foreach (qw/svn_path c r ra /) { + foreach (qw/svn_path r ra/) { die "$_ required!\n" unless (defined $git_svn->{$_}); $self->{$_} = $git_svn->{$_}; } @@ -2868,7 +2897,7 @@ sub url_path { } sub rmdirs { - my ($self, $q) = @_; + my ($self, $tree_b) = @_; my $rm = $self->{rm}; delete $rm->{''}; # we never delete the url we're tracking return unless %$rm; @@ -2887,7 +2916,7 @@ sub rmdirs { return unless %$rm; my ($fh, $ctx) = command_output_pipe( - qw/ls-tree --name-only -r -z/, $self->{c}); + qw/ls-tree --name-only -r -z/, $tree_b); local $/ = "\0"; while (<$fh>) { chomp; @@ -2906,7 +2935,7 @@ sub rmdirs { foreach my $d (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$rm) { $self->close_directory($bat->{$d}, $p); my ($dn) = ($d =~ m#^(.*?)/?(?:[^/]+)$#); - print "\tD+\t$d/\n" unless $q; + print "\tD+\t$d/\n" unless $::_q; $self->SUPER::delete_entry($d, $r, $bat->{$dn}, $p); delete $bat->{$d}; } @@ -2945,23 +2974,23 @@ sub ensure_path { } sub A { - my ($self, $m, $q) = @_; + my ($self, $m) = @_; my ($dir, $file) = split_path($m->{file_b}); my $pbat = $self->ensure_path($dir); my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat, undef, -1); - print "\tA\t$m->{file_b}\n" unless $q; + print "\tA\t$m->{file_b}\n" unless $::_q; $self->chg_file($fbat, $m); $self->close_file($fbat,undef,$self->{pool}); } sub C { - my ($self, $m, $q) = @_; + my ($self, $m) = @_; my ($dir, $file) = split_path($m->{file_b}); my $pbat = $self->ensure_path($dir); my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat, $self->url_path($m->{file_a}), $self->{r}); - print "\tC\t$m->{file_a} => $m->{file_b}\n" unless $q; + print "\tC\t$m->{file_a} => $m->{file_b}\n" unless $::_q; $self->chg_file($fbat, $m); $self->close_file($fbat,undef,$self->{pool}); } @@ -2975,12 +3004,12 @@ sub delete_entry { } sub R { - my ($self, $m, $q) = @_; + my ($self, $m) = @_; my ($dir, $file) = split_path($m->{file_b}); my $pbat = $self->ensure_path($dir); my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat, $self->url_path($m->{file_a}), $self->{r}); - print "\tR\t$m->{file_a} => $m->{file_b}\n" unless $q; + print "\tR\t$m->{file_a} => $m->{file_b}\n" unless $::_q; $self->chg_file($fbat, $m); $self->close_file($fbat,undef,$self->{pool}); @@ -2990,12 +3019,12 @@ sub R { } sub M { - my ($self, $m, $q) = @_; + my ($self, $m) = @_; my ($dir, $file) = split_path($m->{file_b}); my $pbat = $self->ensure_path($dir); my $fbat = $self->open_file($self->repo_path($m->{file_b}), $pbat,$self->{r},$self->{pool}); - print "\t$m->{chg}\t$m->{file_b}\n" unless $q; + print "\t$m->{chg}\t$m->{file_b}\n" unless $::_q; $self->chg_file($fbat, $m); $self->close_file($fbat,undef,$self->{pool}); } @@ -3046,10 +3075,10 @@ sub chg_file { } sub D { - my ($self, $m, $q) = @_; + my ($self, $m) = @_; my ($dir, $file) = split_path($m->{file_b}); my $pbat = $self->ensure_path($dir); - print "\tD\t$m->{file_b}\n" unless $q; + print "\tD\t$m->{file_b}\n" unless $::_q; $self->delete_entry($m->{file_b}, $pbat); } @@ -3069,6 +3098,77 @@ sub abort_edit { $self->{pool}->clear; } +# this drives the editor +sub apply_diff { + my ($self, $tree_a, $tree_b) = @_; + my @diff_tree = qw(diff-tree -z -r); + if ($::_cp_similarity) { + push @diff_tree, "-C$::_cp_similarity"; + } else { + push @diff_tree, '-C'; + } + push @diff_tree, '--find-copies-harder' if $::_find_copies_harder; + push @diff_tree, "-l$::_l" if defined $::_l; + push @diff_tree, $tree_a, $tree_b; + my ($diff_fh, $ctx) = command_output_pipe(@diff_tree); + my $nl = $/; + local $/ = "\0"; + my $state = 'meta'; + my @mods; + while (<$diff_fh>) { + chomp $_; # this gets rid of the trailing "\0" + 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, + sha1_b => $3, chg => $4 }; + if ($4 =~ /^(?:C|R)$/) { + $state = 'file_a'; + } else { + $state = 'file_b'; + } + } elsif ($state eq 'file_a') { + my $x = $mods[$#mods] or croak "Empty array\n"; + if ($x->{chg} !~ /^(?:C|R)$/) { + croak "Error parsing $_, $x->{chg}\n"; + } + $x->{file_a} = $_; + $state = 'file_b'; + } elsif ($state eq 'file_b') { + my $x = $mods[$#mods] or croak "Empty array\n"; + if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) { + croak "Error parsing $_, $x->{chg}\n"; + } + if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) { + croak "Error parsing $_, $x->{chg}\n"; + } + $x->{file_b} = $_; + $state = 'meta'; + } else { + croak "Error parsing $_\n"; + } + } + command_close_pipe($diff_fh, $ctx); + $/ = $nl; + + 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) { + my $f = $m->{chg}; + if (defined $o{$f}) { + $self->$f($m); + } else { + fatal("Invalid change type: $f\n"); + } + } + $self->rmdirs($tree_b) if $::_rmdir; + if (@mods == 0) { + $self->abort_edit; + } else { + $self->close_edit; + } + \@mods; +} + package Git::SVN::Ra; use vars qw/@ISA $config_dir/; use strict; @@ -3144,9 +3244,9 @@ sub get_log { } sub get_commit_editor { - my ($self, $msg, $cb, $pool) = @_; + my ($self, $log, $cb, $pool) = @_; my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : (); - $self->SUPER::get_commit_editor($msg, $cb, @lock, $pool); + $self->SUPER::get_commit_editor($log, $cb, @lock, $pool); } sub uuid { @@ -3211,13 +3311,13 @@ sub cmt_showable { return 1 if defined $c->{r}; if ($c->{l} && $c->{l}->[-1] eq "...\n" && $c->{a_raw} =~ /\@([a-f\d\-]+)>$/) { - my @msg = command(qw/cat-file commit/, $c->{c}); - shift @msg while ($msg[0] ne "\n"); - shift @msg; - @{$c->{l}} = grep !/^git-svn-id: /, @msg; + my @log = command(qw/cat-file commit/, $c->{c}); + shift @log while ($log[0] ne "\n"); + shift @log; + @{$c->{l}} = grep !/^git-svn-id: /, @log; (undef, $c->{r}, undef) = ::extract_metadata( - (grep(/^git-svn-id: /, @msg))[-1]); + (grep(/^git-svn-id: /, @log))[-1]); } return defined $c->{r}; } @@ -3503,9 +3603,9 @@ sub cmd_show_log { Data structures: -$log_msg hashref as returned by libsvn_log_entry() +$log_entry hashref as returned by libsvn_log_entry() { - msg => 'whitespace-formatted log entry + log => 'whitespace-formatted log entry ', # trailing newline is preserved revision => '8', # integer date => '2004-02-24T17:01:44.108345Z', # commit date From 396988e0b9cd00d5d13edb157b91dbd5050ef99f Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 13 Jan 2007 23:00:47 -0800 Subject: [PATCH 076/201] git-svn: get rid of Memoize for now... I may refactor more of this stuff into separate modules --- git-svn.perl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 575d7936db..acc93b9c10 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -50,11 +50,7 @@ use File::Path qw/mkpath/; use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev pass_through/; use IPC::Open3; -use Memoize; use Git; -memoize('revisions_eq'); -memoize('cmt_metadata'); -memoize('get_commit_time'); BEGIN { my $s; From 1c8443b05074cfa466845c4dad98fe962f6dd4c2 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 14 Jan 2007 02:17:00 -0800 Subject: [PATCH 077/201] git-svn: fetch/multi-fetch converted over to Git::SVN module --follow-parent and commit-diff are currently broken with this commit... Signed-off-by: Eric Wong --- git-svn.perl | 89 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 51 insertions(+), 38 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index acc93b9c10..bc3504fd9f 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -9,7 +9,7 @@ $GIT_DIR $GIT_SVN_DIR $REVDB $_follow_parent $sha1 $sha1_short $_revision $_cp_remote $_upgrade $_rmdir $_q $_cp_similarity - $_find_copies_harder $_l/; + $_find_copies_harder $_l $_authors %users/; $AUTHOR = 'Eric Wong '; $VERSION = '@@GIT_VERSION@@'; @@ -71,10 +71,10 @@ BEGIN $_repack, $_repack_nr, $_repack_flags, $_message, $_file, $_no_metadata, $_template, $_shared, $_no_default_regex, $_no_graft_copy, - $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m, + $_version, $_upgrade, $_branch_all_refs, @_opt_m, $_merge, $_strategy, $_dry_run, $_prefix); -my (@_branch_from, %tree_map, %users); +my (@_branch_from, %tree_map); my @repo_path_split_cache; my %fc_opts = ( 'branch|b=s' => \@_branch_from, @@ -135,7 +135,7 @@ BEGIN 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache, 'prefix=s' => \$_prefix, } ], - 'multi-fetch' => [ \&multi_fetch, + 'multi-fetch' => [ \&cmd_multi_fetch, 'Fetch multiple trees (like git-svnimport)', \%fc_opts ], 'log' => [ \&Git::SVN::Log::cmd_show_log, 'Show commit logs', @@ -292,7 +292,12 @@ sub cmd_init { } sub cmd_fetch { - fetch_child_id($GIT_SVN, @_); + my $gs = Git::SVN->new; + $gs->fetch(@_); + if ($gs->{last_commit} && !verify_ref('refs/heads/master^0')) { + command_noisy(qw(update-ref refs/heads/master), + $gs->{last_commit}); + } } sub fetch { @@ -583,13 +588,14 @@ sub cmd_multi_init { complete_url_ls_init($ra, $_tags, '--tags/-t', $_prefix . 'tags/'); } -sub multi_fetch { +sub cmd_multi_fetch { # try to do trunk first, since branches/tags # may be descended from it. - if (-e "$GIT_DIR/svn/trunk/info/url") { - fetch_child_id('trunk', @_); + if (-e "$ENV{GIT_DIR}/svn/trunk/info/url") { + my $gs = Git::SVN->new('trunk'); + $gs->fetch(@_); } - rec_fetch('', "$GIT_DIR/svn", @_); + rec_fetch('', "$ENV{GIT_DIR}/svn", @_); } # this command is special because it requires no metadata @@ -706,24 +712,6 @@ sub commit_diff { ########################### utility functions ######################### -sub fetch_child_id { - my $id = shift; - print "Fetching $id\n"; - my $ref = "$GIT_DIR/refs/remotes/$id"; - defined(my $pid = open my $fh, '-|') or croak $!; - if (!$pid) { - $GIT_SVN = $ENV{GIT_SVN_ID} = $id; - init_vars(); - fetch(@_); - exit 0; - } - while (<$fh>) { - print $_; - check_repack() if (/^r\d+ = $sha1/o); - } - close $fh or croak $?; -} - sub rec_fetch { my ($pfx, $p, @args) = @_; my @dir; @@ -732,15 +720,16 @@ sub rec_fetch { $pfx .= '/' if $pfx && $pfx !~ m!/$!; my $id = $pfx . basename $_; next if $id eq 'trunk'; - fetch_child_id($id, @args); + my $gs = Git::SVN->new($id); + $gs->fetch(@args); } elsif (-d $_) { push @dir, $_; } } foreach (@dir) { my $x = $_; - $x =~ s!^\Q$GIT_DIR\E/svn/!!; - rec_fetch($x, $_); + $x =~ s!^\Q$ENV{GIT_DIR}\E/svn/!!o; + rec_fetch($x, $_, @args); } } @@ -1841,8 +1830,9 @@ sub do_git_commit { croak "$log_entry->{revision} = $c already exists! ", "Why are we refetching it?\n"; } - my ($name, $email) = ::author_name_email($log_entry->{author}, - $self->ra); + my $author = $log_entry->{author}; + my ($name, $email) = (defined $::users{$author} ? @{$::users{$author}} + : ($author, "$author\@".$self->ra->uuid)); $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name; $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email; $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_entry->{date}; @@ -1894,7 +1884,7 @@ sub do_fetch { } else { $last_rev = $rev; } - unless ($self->ra->do_update($last_rev, $rev, '', 1, $ed)) { + unless ($self->ra->gs_do_update($last_rev, $rev, '', 1, $ed)) { die "SVN connection failed somewhere...\n"; } $self->make_log_entry($rev, \@parents, $ed); @@ -1946,6 +1936,25 @@ sub write_untracked { } } +sub parse_svn_date { + my $date = shift || return '+0000 1970-01-01 00:00:00'; + my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T + (\d\d)\:(\d\d)\:(\d\d).\d+Z$/x) or + croak "Unable to parse date: $date\n"; + "+0000 $Y-$m-$d $H:$M:$S"; +} + +sub check_author { + my ($author) = @_; + if (!defined $author || length $author == 0) { + $author = '(no author)'; + } + if (defined $::_authors && ! defined $::users{$author}) { + die "Author: $author not defined in $::_authors file\n"; + } + $author; +} + sub make_log_entry { my ($self, $rev, $parents, $untracked) = @_; my $rp = $self->ra->rev_proplist($rev); @@ -2105,6 +2114,11 @@ sub _new { db_path => "$dir/.rev_db" }, $class; } +sub uri_encode { + my ($f) = @_; + $f =~ s#([^a-zA-Z0-9\*!\:_\./\-])#uc sprintf("%%%02x",ord($1))#eg; + $f +} package Git::SVN::Prompt; use strict; @@ -2662,15 +2676,14 @@ sub new { my ($class, $git_svn) = @_; my $self = SVN::Delta::Editor->new; bless $self, $class; - $self->{c} = $git_svn->{c} if exists $git_svn->{c}; - $self->{q} = $git_svn->{q}; + $self->{c} = $git_svn->{last_commit} if exists $git_svn->{last_commit}; $self->{empty} = {}; $self->{dir_prop} = {}; $self->{file_prop} = {}; $self->{absent_dir} = {}; $self->{absent_file} = {}; - ($self->{gui}, $self->{ctx}) = command_input_pipe( - qw/update-index -z --index-info/); + ($self->{gui}, $self->{ctx}) = $git_svn->tmp_index_do( + sub { command_input_pipe(qw/update-index -z --index-info/) } ); require Digest::MD5; $self; } @@ -3416,7 +3429,7 @@ sub get_author_info { $author =~ s/(?:^\s*|\s*$)//g; $dest->{a_raw} = $author; my $au; - if ($_authors) { + if ($::_authors) { $au = $rusers{$author} || undef; } if (!$au) { From d7ad3bed8cfbaf21aeaaff2cd10e3696d8785b78 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 14 Jan 2007 03:14:28 -0800 Subject: [PATCH 078/201] git-svn: switch dcommit to using Git::SVN code Signed-off-by: Eric Wong --- git-svn.perl | 122 ++++++++++++++++----------------------------------- 1 file changed, 38 insertions(+), 84 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index bc3504fd9f..bf53b2d69b 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -107,7 +107,8 @@ BEGIN init => [ \&cmd_init, "Initialize a repo for tracking" . " (requires URL argument)", \%init_opts ], - dcommit => [ \&dcommit, 'Commit several diffs to merge with upstream', + dcommit => [ \&cmd_dcommit, + 'Commit several diffs to merge with upstream', { 'merge|m|M' => \$_merge, 'strategy|s=s' => \$_strategy, 'dry-run|n' => \$_dry_run, @@ -482,49 +483,65 @@ sub commit_lib { unlink $commit_msg; } -sub dcommit { - my $head = shift || 'HEAD'; - my $gs = "refs/remotes/$GIT_SVN"; - my @refs = command(qw/rev-list --no-merges/, "$gs..$head"); +sub cmd_dcommit { + my $head = shift; + my $gs = Git::SVN->new; + $head ||= 'HEAD'; + my @refs = command(qw/rev-list --no-merges/, $gs->refname."..$head"); my $last_rev; foreach my $d (reverse @refs) { if (!verify_ref("$d~1")) { - die "Commit $d\n", - "has no parent commit, and therefore ", - "nothing to diff against.\n", - "You should be working from a repository ", - "originally created by git-svn\n"; + fatal "Commit $d\n", + "has no parent commit, and therefore ", + "nothing to diff against.\n", + "You should be working from a repository ", + "originally created by git-svn\n"; } unless (defined $last_rev) { (undef, $last_rev, undef) = cmt_metadata("$d~1"); unless (defined $last_rev) { - die "Unable to extract revision information ", - "from commit $d~1\n"; + fatal "Unable to extract revision information ", + "from commit $d~1\n"; } } if ($_dry_run) { print "diff-tree $d~1 $d\n"; } else { - if (my $r = commit_diff("$d~1", $d, undef, $last_rev)) { - $last_rev = $r; - } # else: no changes, same $last_rev + my $ra = $gs->ra; + my $pool = SVN::Pool->new; + my %ed_opts = ( r => $last_rev, + ra => $ra->dup, + svn_path => $ra->{svn_path} ); + my $ed = SVN::Git::Editor->new(\%ed_opts, + $ra->get_commit_editor($::_message, + sub { print "Committed r$_[0]\n"; + $last_rev = $_[0]; }), + $pool); + my $mods = $ed->apply_diff("$d~1", $d); + if (@$mods == 0) { + print "No changes\n$d~1 == $d\n"; + } } } return if $_dry_run; - fetch(); - my @diff = command('diff-tree', 'HEAD', $gs, '--'); + $gs->fetch; + # we always want to rebase against the current HEAD, not any + # head that was passed to us + my @diff = command('diff-tree', 'HEAD', $gs->refname, '--'); my @finish; if (@diff) { @finish = qw/rebase/; push @finish, qw/--merge/ if $_merge; push @finish, "--strategy=$_strategy" if $_strategy; - print STDERR "W: HEAD and $gs differ, using @finish:\n", @diff; + print STDERR "W: HEAD and ", $gs->refname, " differ, ", + "using @finish:\n", "@diff"; } else { - print "No changes between current HEAD and $gs\n", - "Resetting to the latest $gs\n"; + print "No changes between current HEAD and ", + $gs->refname, "\nResetting to the latest ", + $gs->refname, "\n"; @finish = qw/reset --mixed/; } - command_noisy(@finish, $gs); + command_noisy(@finish, $gs->refname); } sub cmd_show_ignore { @@ -647,69 +664,6 @@ sub cmd_commit_diff { $pool->clear; } -sub commit_diff_usage { - print STDERR "Usage: $0 commit-diff []\n"; - exit 1 -} - -sub commit_diff { - my $ta = shift or commit_diff_usage(); - my $tb = shift or commit_diff_usage(); - if (!eval { $SVN_URL = shift || file_to_s("$GIT_SVN_DIR/info/url") }) { - print STDERR "Needed URL or usable git-svn id command-line\n"; - commit_diff_usage(); - } - my $r = shift; - unless (defined $r) { - if (defined $_revision) { - $r = $_revision - } else { - die "-r|--revision is a required argument\n"; - } - } - if (defined $_message && defined $_file) { - print STDERR "Both --message/-m and --file/-F specified ", - "for the commit message.\n", - "I have no idea what you mean\n"; - exit 1; - } - if (defined $_file) { - $_message = file_to_s($_file); - } else { - $_message ||= get_commit_entry($tb, - "$GIT_DIR/.svn-commit.tmp.$$")->{log}; - } - $SVN ||= Git::SVN::Ra->new($SVN_URL); - if ($r eq 'HEAD') { - $r = $SVN->get_latest_revnum; - } elsif ($r !~ /^\d+$/) { - die "revision argument: $r not understood by git-svn\n"; - } - my $rev_committed; - my $pool = SVN::Pool->new; - my $ed = SVN::Git::Editor->new({ r => $r, - ra => $SVN->dup, - svn_path => $SVN->{svn_path} - }, - $SVN->get_commit_editor($_message, - sub { - $rev_committed = $_[0]; - print "Committed $_[0]\n"; - }, - $pool) - ); - eval { - my $mods = $ed->apply_diff($ta, $tb); - if (@$mods == 0) { - print "No changes\n$ta == $tb\n"; - } - }; - $pool->clear; - fatal "$@\n" if $@; - $_message = $_file = undef; - return $rev_committed; -} - ########################### utility functions ######################### sub rec_fetch { From 1ce255dc168cc1fcf849a7c82bdf45753b0dfe09 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 14 Jan 2007 23:21:16 -0800 Subject: [PATCH 079/201] git-svn: convert 'set-tree' command to use Git::SVN Signed-off-by: Eric Wong --- git-svn.perl | 512 ++------------------------------------------------- 1 file changed, 17 insertions(+), 495 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index bf53b2d69b..261e33d023 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -24,16 +24,6 @@ $ENV{LC_ALL} = 'C'; $| = 1; # unbuffer STDOUT -# properties that we do not log: -my %SKIP = ( 'svn:wc:ra_dav:version-url' => 1, - 'svn:special' => 1, - 'svn:executable' => 1, - 'svn:entry:committed-rev' => 1, - 'svn:entry:last-author' => 1, - 'svn:entry:uuid' => 1, - 'svn:entry:committed-date' => 1, -); - sub fatal (@) { print STDERR @_; exit 1 } require SVN::Core; # use()-ing this causes segfaults for me... *shrug* require SVN::Ra; @@ -113,8 +103,9 @@ BEGIN 'strategy|s=s' => \$_strategy, 'dry-run|n' => \$_dry_run, %cmt_opts, %fc_opts } ], - 'set-tree' => [ \&commit, "Set an SVN repository to a git tree-ish", - { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ], + 'set-tree' => [ \&cmd_set_tree, + "Set an SVN repository to a git tree-ish", + { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ], 'show-ignore' => [ \&cmd_show_ignore, "Show svn:ignore listings", { 'revision|r=i' => \$_revision } ], rebuild => [ \&cmd_rebuild, "Rebuild git-svn metadata (after git clone)", @@ -301,94 +292,8 @@ sub cmd_fetch { } } -sub fetch { - check_upgrade_needed(); - $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url"); - my $ret = fetch_lib(@_); - if ($ret->{commit} && !verify_ref('refs/heads/master^0')) { - command_noisy(qw(update-ref refs/heads/master),$ret->{commit}); - } - return $ret; -} - -sub fetch_lib { - my (@parents) = @_; - $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url"); - $SVN ||= Git::SVN::Ra->new($SVN_URL); - my ($last_rev, $last_commit) = svn_grab_base_rev(); - my ($base, $head) = libsvn_parse_revision($last_rev); - if ($base > $head) { - return { revision => $last_rev, commit => $last_commit } - } - my $index = set_index($GIT_SVN_INDEX); - - # limit ourselves and also fork() since get_log won't release memory - # after processing a revision and SVN stuff seems to leak - my $inc = 1000; - my ($min, $max) = ($base, $head < $base+$inc ? $head : $base+$inc); - if (defined $last_commit) { - unless (-e $GIT_SVN_INDEX) { - command_noisy('read-tree', $last_commit); - } - my $x = command_oneline('write-tree'); - my ($y) = (command(qw/cat-file commit/, $last_commit) - =~ /^tree ($sha1)/m); - if ($y ne $x) { - unlink $GIT_SVN_INDEX or croak $!; - command_noisy('read-tree', $last_commit); - } - $x = command_oneline('write-tree'); - if ($y ne $x) { - print STDERR "trees ($last_commit) $y != $x\n", - "Something is seriously wrong...\n"; - } - } - while (1) { - # fork, because using SVN::Pool with get_log() still doesn't - # seem to help enough to keep memory usage down. - defined(my $pid = fork) or croak $!; - if (!$pid) { - $SVN::Error::handler = \&libsvn_skip_unknown_revs; - - # Yes I'm perfectly aware that the fourth argument - # below is the limit revisions number. Unfortunately - # performance sucks with it enabled, so it's much - # faster to fetch revision ranges instead of relying - # on the limiter. - $SVN->dup->get_log([''], $min, $max, 0, 1, 1, - sub { - my $log_entry; - if ($last_commit) { - $log_entry = libsvn_fetch( - $last_commit, @_); - $last_commit = git_commit( - $log_entry, - $last_commit, - @parents); - } else { - $log_entry = libsvn_new_tree(@_); - $last_commit = git_commit( - $log_entry, @parents); - } - }); - exit 0; - } - waitpid $pid, 0; - croak $? if $?; - ($last_rev, $last_commit) = svn_grab_base_rev(); - last if ($max >= $head); - $min = $max + 1; - $max += $inc; - $max = $head if ($max > $head); - $SVN = Git::SVN::Ra->new($SVN_URL); - } - restore_index($index); - return { revision => $last_rev, commit => $last_commit }; -} - -sub commit { +sub cmd_set_tree { my (@commits) = @_; - check_upgrade_needed(); if ($_stdin || !@commits) { print "Reading from stdin...\n"; @commits = (); @@ -406,83 +311,22 @@ sub commit { } elsif (scalar @tmp > 1) { push @revs, reverse(command('rev-list',@tmp)); } else { - die "Failed to rev-parse $c\n"; + fatal "Failed to rev-parse $c\n"; } } - commit_lib(@revs); + my $gs = Git::SVN->new; + my ($r_last, $cmt_last) = $gs->last_rev_commit; + $gs->fetch; + if ($r_last != $gs->{last_rev}) { + fatal "There are new revisions that were fetched ", + "and need to be merged (or acknowledged) ", + "before committing.\nlast rev: $r_last\n", + " current: $gs->{last_rev}\n"; + } + $gs->set_tree($_) foreach @revs; print "Done committing ",scalar @revs," revisions to SVN\n"; } -sub commit_lib { - my (@revs) = @_; - my ($r_last, $cmt_last) = svn_grab_base_rev(); - defined $r_last or die "Must have an existing revision to commit\n"; - my $fetched = fetch(); - if ($r_last != $fetched->{revision}) { - print STDERR "There are new revisions that were fetched ", - "and need to be merged (or acknowledged) ", - "before committing.\n", - "last rev: $r_last\n", - " current: $fetched->{revision}\n"; - exit 1; - } - my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$"; - - my $repo; - set_svn_commit_env(); - foreach my $c (@revs) { - my $log_entry = get_commit_entry($c, $commit_msg); - - # fork for each commit because there's a memory leak I - # can't track down... (it's probably in the SVN code) - defined(my $pid = open my $fh, '-|') or croak $!; - if (!$pid) { - my $pool = SVN::Pool->new; - my $ed = SVN::Git::Editor->new( - { r => $r_last, - ra => $SVN->dup, - svn_path => $SVN->{svn_path}, - }, - $SVN->get_commit_editor( - $log_entry->{log}, - sub { - libsvn_commit_cb( - @_, $c, - $log_entry->{log}, - $r_last, - $cmt_last) - }, $pool) - ); - my $mods = $ed->apply_diff($cmt_last, $c); - if (@$mods == 0) { - print "No changes\nr$r_last = $cmt_last\n"; - } - $pool->clear; - exit 0; - } - my ($r_new, $cmt_new, $no); - while (<$fh>) { - print $_; - chomp; - if (/^r(\d+) = ($sha1)$/o) { - ($r_new, $cmt_new) = ($1, $2); - } elsif ($_ eq 'No changes') { - $no = 1; - } - } - close $fh or exit 1; - if (! defined $r_new && ! defined $cmt_new) { - unless ($no) { - die "Failed to parse revision information\n"; - } - } else { - ($r_last, $cmt_last) = ($r_new, $cmt_new); - } - } - $ENV{LC_ALL} = 'C'; - unlink $commit_msg; -} - sub cmd_dcommit { my $head = shift; my $gs = Git::SVN->new; @@ -1055,14 +899,6 @@ sub get_commit_entry { \%log_entry; } -sub set_svn_commit_env { - if (defined $LC_ALL) { - $ENV{LC_ALL} = $LC_ALL; - } else { - delete $ENV{LC_ALL}; - } -} - sub rev_list_raw { my ($fh, $c) = command_output_pipe(qw/rev-list --pretty=raw/, @_); return { fh => $fh, ctx => $c, t => { } }; @@ -1109,124 +945,6 @@ sub file_to_s { return $ret; } -sub assert_revision_unknown { - my $r = shift; - if (my $c = revdb_get($REVDB, $r)) { - croak "$r = $c already exists! Why are we refetching it?"; - } -} - -sub git_commit { - my ($log_entry, @parents) = @_; - assert_revision_unknown($log_entry->{revision}); - map_tree_joins() if (@_branch_from && !%tree_map); - - my (@tmp_parents, @exec_parents, %seen_parent); - if (my $lparents = $log_entry->{parents}) { - @tmp_parents = @$lparents - } - # commit parents can be conditionally bound to a particular - # svn revision via: "svn_revno=commit_sha1", filter them out here: - foreach my $p (@parents) { - next unless defined $p; - if ($p =~ /^(\d+)=($sha1_short)$/o) { - if ($1 == $log_entry->{revision}) { - push @tmp_parents, $2; - } - } else { - push @tmp_parents, $p if $p =~ /$sha1_short/o; - } - } - my $tree = $log_entry->{tree}; - if (!defined $tree) { - my $index = set_index($GIT_SVN_INDEX); - $tree = command_oneline('write-tree'); - croak $? if $?; - restore_index($index); - } - # just in case we clobber the existing ref, we still want that ref - # as our parent: - if (my $cur = verify_ref("refs/remotes/$GIT_SVN^0")) { - chomp $cur; - push @tmp_parents, $cur; - } - - if (exists $tree_map{$tree}) { - foreach my $p (@{$tree_map{$tree}}) { - my $skip; - foreach (@tmp_parents) { - # see if a common parent is found - my $mb = eval { command('merge-base', $_, $p) }; - next if ($@ || $?); - $skip = 1; - last; - } - next if $skip; - my ($url_p, $r_p, $uuid_p) = cmt_metadata($p); - next if (($SVN->uuid eq $uuid_p) && - ($log_entry->{revision} > $r_p)); - next if (defined $url_p && defined $SVN_URL && - ($SVN->uuid eq $uuid_p) && - ($url_p eq $SVN_URL)); - push @tmp_parents, $p; - } - } - foreach (@tmp_parents) { - next if $seen_parent{$_}; - $seen_parent{$_} = 1; - push @exec_parents, $_; - # MAXPARENT is defined to 16 in commit-tree.c: - last if @exec_parents > 16; - } - - set_commit_env($log_entry); - my @exec = ('git-commit-tree', $tree); - push @exec, '-p', $_ foreach @exec_parents; - defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec)) - or croak $!; - print $msg_fh $log_entry->{log} or croak $!; - unless ($_no_metadata) { - print $msg_fh "\ngit-svn-id: $SVN_URL\@$log_entry->{revision} ", - $SVN->uuid,"\n" or croak $!; - } - $msg_fh->flush == 0 or croak $!; - close $msg_fh or croak $!; - chomp(my $commit = do { local $/; <$out_fh> }); - close $out_fh or croak $!; - waitpid $pid, 0; - croak $? if $?; - if ($commit !~ /^$sha1$/o) { - die "Failed to commit, invalid sha1: $commit\n"; - } - command_noisy('update-ref',"refs/remotes/$GIT_SVN",$commit); - revdb_set($REVDB, $log_entry->{revision}, $commit); - - # this output is read via pipe, do not change: - print "r$log_entry->{revision} = $commit\n"; - return $commit; -} - -sub check_repack { - if ($_repack && (--$_repack_nr == 0)) { - $_repack_nr = $_repack; - # repack doesn't use any arguments with spaces in them, does it? - command_noisy('repack', split(/\s+/, $_repack_flags)); - } -} - -sub set_commit_env { - my ($log_entry) = @_; - my $author = $log_entry->{author}; - if (!defined $author || length $author == 0) { - $author = '(no author)'; - } - my ($name,$email) = defined $users{$author} ? @{$users{$author}} - : ($author,$author . '@' . $SVN->uuid); - $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name; - $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email; - $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_entry->{date}; -} - sub check_upgrade_needed { if (!-r $REVDB) { -d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]); @@ -1859,7 +1577,7 @@ sub write_untracked { foreach my $path (sort keys %$h) { my $ppath = $path eq '' ? '.' : $path; foreach my $prop (sort keys %{$h->{$path}}) { - next if $SKIP{$prop}; + next if $SKIP_PROP{$prop}; my $v = $h->{$path}->{$prop}; if (defined $v) { print $fh " +$t: ", @@ -1975,7 +1693,7 @@ sub set_tree_cb { sub set_tree { my ($self, $tree) = (shift, shift); - my $log_entry = get_commit_entry($tree); + my $log_entry = ::get_commit_entry($tree); unless ($self->{last_rev}) { fatal("Must have an existing revision to commit\n"); } @@ -2218,118 +1936,6 @@ sub uri_decode { $f } -sub libsvn_log_entry { - my ($rev, $author, $date, $log, $parents, $untracked) = @_; - my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T - (\d\d)\:(\d\d)\:(\d\d).\d+Z$/x) - or die "Unable to parse date: $date\n"; - if (defined $author && length $author > 0 && - defined $_authors && ! defined $users{$author}) { - die "Author: $author not defined in $_authors file\n"; - } - $log = '' if ($rev == 0 && !defined $log); - - open my $un, '>>', "$GIT_SVN_DIR/unhandled.log" or croak $!; - my $h; - print $un "r$rev\n" or croak $!; - $h = $untracked->{empty}; - foreach (sort keys %$h) { - my $act = $h->{$_} ? '+empty_dir' : '-empty_dir'; - print $un " $act: ", uri_encode($_), "\n" or croak $!; - warn "W: $act: $_\n"; - } - foreach my $t (qw/dir_prop file_prop/) { - $h = $untracked->{$t} or next; - foreach my $path (sort keys %$h) { - my $ppath = $path eq '' ? '.' : $path; - foreach my $prop (sort keys %{$h->{$path}}) { - next if $SKIP{$prop}; - my $v = $h->{$path}->{$prop}; - if (defined $v) { - print $un " +$t: ", - uri_encode($ppath), ' ', - uri_encode($prop), ' ', - uri_encode($v), "\n" - or croak $!; - } else { - print $un " -$t: ", - uri_encode($ppath), ' ', - uri_encode($prop), "\n" - or croak $!; - } - } - } - } - foreach my $t (qw/absent_file absent_directory/) { - $h = $untracked->{$t} or next; - foreach my $parent (sort keys %$h) { - foreach my $path (sort @{$h->{$parent}}) { - print $un " $t: ", - uri_encode("$parent/$path"), "\n" - or croak $!; - warn "W: $t: $parent/$path ", - "Insufficient permissions?\n"; - } - } - } - - # revprops (make this optional? it's an extra network trip...) - my $rp = $SVN->rev_proplist($rev); - foreach (sort keys %$rp) { - next if /^svn:(?:author|date|log)$/; - print $un " rev_prop: ", uri_encode($_), ' ', - uri_encode($rp->{$_}), "\n"; - } - close $un or croak $!; - - { revision => $rev, date => "+0000 $Y-$m-$d $H:$M:$S", - author => $author, log => $log."\n", parents => $parents || [], - revprops => $rp } -} - -sub libsvn_fetch { - my ($last_commit, $paths, $rev, $author, $date, $log) = @_; - my $ed = SVN::Git::Fetcher->new({ c => $last_commit, q => $_q }); - my (undef, $last_rev, undef) = cmt_metadata($last_commit); - unless ($SVN->gs_do_update($last_rev, $rev, '', 1, $ed)) { - die "SVN connection failed somewhere...\n"; - } - libsvn_log_entry($rev, $author, $date, $log, [$last_commit], $ed); -} - -sub svn_grab_base_rev { - my $c = eval { command_oneline([qw/rev-parse --verify/, - "refs/remotes/$GIT_SVN^0"], - { STDERR => 0 }) }; - if (defined $c && length $c) { - my ($url, $rev, $uuid) = cmt_metadata($c); - return ($rev, $c) if defined $rev; - } - if ($_no_metadata) { - my $offset = -41; # from tail - my $rl; - open my $fh, '<', $REVDB or - die "--no-metadata specified and $REVDB not readable\n"; - seek $fh, $offset, 2; - $rl = readline $fh; - defined $rl or return (undef, undef); - chomp $rl; - while ($c ne $rl && tell $fh != 0) { - $offset -= 41; - seek $fh, $offset, 2; - $rl = readline $fh; - defined $rl or return (undef, undef); - chomp $rl; - } - my $rev = tell $fh; - croak $! if ($rev < -1); - $rev = ($rev - 41) / 41; - close $fh or croak $!; - return ($rev, $c); - } - return (undef, undef); -} - sub libsvn_parse_revision { my $base = shift; my $head = $SVN->get_latest_revnum(); @@ -2450,14 +2056,6 @@ sub libsvn_find_parent_branch { return undef; } -sub libsvn_new_tree { - if (my $log_entry = libsvn_find_parent_branch(@_)) { - return $log_entry; - } - my ($paths, $rev, $author, $date, $log) = @_; # $pool is last - _libsvn_new_tree($paths, $rev, $author, $date, $log, []); -} - sub _libsvn_new_tree { my ($paths, $rev, $author, $date, $log, $parents) = @_; my $ed = SVN::Git::Fetcher->new({q => $_q}); @@ -2513,82 +2111,6 @@ sub libsvn_graft_file_copies { } } -sub set_index { - my $old = $ENV{GIT_INDEX_FILE}; - $ENV{GIT_INDEX_FILE} = shift; - return $old; -} - -sub restore_index { - my ($old) = @_; - if (defined $old) { - $ENV{GIT_INDEX_FILE} = $old; - } else { - delete $ENV{GIT_INDEX_FILE}; - } -} - -sub libsvn_commit_cb { - my ($rev, $date, $committer, $c, $log, $r_last, $cmt_last) = @_; - if ($_optimize_commits && $rev == ($r_last + 1)) { - my $log = libsvn_log_entry($rev,$committer,$date,$log); - $log->{tree} = get_tree_from_treeish($c); - my $cmt = git_commit($log, $cmt_last, $c); - my @diff = command('diff-tree', $cmt, $c); - if (@diff) { - print STDERR "Trees differ: $cmt $c\n", - join('',@diff),"\n"; - exit 1; - } - } else { - fetch("$rev=$c"); - } -} - -sub libsvn_skip_unknown_revs { - my $err = shift; - my $errno = $err->apr_err(); - # Maybe the branch we're tracking didn't - # exist when the repo started, so it's - # not an error if it doesn't, just continue - # - # Wonderfully consistent library, eh? - # 160013 - svn:// and file:// - # 175002 - http(s):// - # 175007 - http(s):// (this repo required authorization, too...) - # More codes may be discovered later... - if ($errno == 175007 || $errno == 175002 || $errno == 160013) { - return; - } - croak "Error from SVN, ($errno): ", $err->expanded_message,"\n"; -}; - -# Tie::File seems to be prone to offset errors if revisions get sparse, -# it's not that fast, either. Tie::File is also not in Perl 5.6. So -# one of my favorite modules is out :< Next up would be one of the DBM -# modules, but I'm not sure which is most portable... So I'll just -# go with something that's plain-text, but still capable of -# being randomly accessed. So here's my ultra-simple fixed-width -# database. All records are 40 characters + "\n", so it's easy to seek -# to a revision: (41 * rev) is the byte offset. -# A record of 40 0s denotes an empty revision. -# And yes, it's still pretty fast (faster than Tie::File). -sub revdb_set { - my ($file, $rev, $commit) = @_; - length $commit == 40 or croak "arg3 must be a full SHA1 hexsum\n"; - open my $fh, '+<', $file or croak $!; - my $offset = $rev * 41; - # assume that append is the common case: - seek $fh, 0, 2 or croak $!; - my $pos = tell $fh; - if ($pos < $offset) { - print $fh (('0' x 40),"\n") x (($offset - $pos) / 41); - } - seek $fh, $offset, 0 or croak $!; - print $fh $commit,"\n"; - close $fh or croak $!; -} - sub revdb_get { my ($file, $rev) = @_; my $ret; From d05d72e07e49869fe988d4d99e6ac60711570db5 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 15 Jan 2007 22:59:26 -0800 Subject: [PATCH 080/201] git-svn: remove graft-branches command It's becoming a maintenance burden. I've never found it particularly useful myself, nor have I heard much feedback about it; so I'm assuming it's just as useless to everyone else. Signed-off-by: Eric Wong --- git-svn.perl | 529 +----------------------------- t/t9103-git-svn-graft-branches.sh | 67 ---- 2 files changed, 3 insertions(+), 593 deletions(-) delete mode 100755 t/t9103-git-svn-graft-branches.sh diff --git a/git-svn.perl b/git-svn.perl index 261e33d023..e75021bce2 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -60,16 +60,13 @@ BEGIN my ($_stdin, $_help, $_edit, $_repack, $_repack_nr, $_repack_flags, $_message, $_file, $_no_metadata, - $_template, $_shared, $_no_default_regex, $_no_graft_copy, - $_version, $_upgrade, $_branch_all_refs, @_opt_m, + $_template, $_shared, + $_version, $_upgrade, $_merge, $_strategy, $_dry_run, $_prefix); -my (@_branch_from, %tree_map); my @repo_path_split_cache; -my %fc_opts = ( 'branch|b=s' => \@_branch_from, - 'follow-parent|follow' => \$_follow_parent, - 'branch-all-refs|B' => \$_branch_all_refs, +my %fc_opts = ( 'follow-parent|follow' => \$_follow_parent, 'authors-file|A=s' => \$_authors, 'repack:i' => \$_repack, 'no-metadata' => \$_no_metadata, @@ -111,13 +108,6 @@ BEGIN rebuild => [ \&cmd_rebuild, "Rebuild git-svn metadata (after git clone)", { 'copy-remote|remote=s' => \$_cp_remote, 'upgrade' => \$_upgrade } ], - 'graft-branches' => [ \&graft_branches, - 'Detect merges/branches from already imported history', - { 'merge-rx|m' => \@_opt_m, - 'branch|b=s' => \@_branch_from, - 'branch-all-refs|B' => \$_branch_all_refs, - 'no-default-regex' => \$_no_default_regex, - 'no-graft-copy' => \$_no_graft_copy } ], 'multi-init' => [ \&cmd_multi_init, 'Initialize multiple trees (like git-svnimport)', { %multi_opts, %init_opts, @@ -167,13 +157,11 @@ BEGIN 'id|i=s' => \$GIT_SVN); exit 1 if (!$rv && $cmd ne 'log'); -set_default_vals(); usage(0) if $_help; version() if $_version; usage(1) unless defined $cmd; init_vars(); load_authors() if $_authors; -load_all_refs() if $_branch_all_refs; migration_check() unless $cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/; $cmd{$cmd}->[0]->(@ARGV); exit 0; @@ -394,40 +382,6 @@ sub cmd_show_ignore { $gs->traverse_ignore(\*STDOUT, '', $r); } -sub graft_branches { - my $gr_file = "$GIT_DIR/info/grafts"; - my ($grafts, $comments) = read_grafts($gr_file); - my $gr_sha1; - - if (%$grafts) { - # temporarily disable our grafts file to make this idempotent - chomp($gr_sha1 = command(qw/hash-object -w/,$gr_file)); - rename $gr_file, "$gr_file~$gr_sha1" or croak $!; - } - - my $l_map = read_url_paths(); - my @re = map { qr/$_/is } @_opt_m if @_opt_m; - unless ($_no_default_regex) { - push @re, (qr/\b(?:merge|merging|merged)\s+with\s+([\w\.\-]+)/i, - qr/\b(?:merge|merging|merged)\s+([\w\.\-]+)/i, - qr/\b(?:from|of)\s+([\w\.\-]+)/i ); - } - foreach my $u (keys %$l_map) { - if (@re) { - foreach my $p (keys %{$l_map->{$u}}) { - graft_merge_msg($grafts,$l_map,$u,$p,@re); - } - } - unless ($_no_graft_copy) { - graft_file_copy_lib($grafts,$l_map,$u); - } - } - graft_tree_joins($grafts); - - write_grafts($grafts, $comments, $gr_file); - unlink "$gr_file~$gr_sha1" if $gr_sha1; -} - sub cmd_multi_init { my $url = shift; unless (defined $_trunk || defined $_branches || defined $_tags) { @@ -601,157 +555,6 @@ sub common_prefix { return ''; } -# grafts set here are 'stronger' in that they're based on actual tree -# matches, and won't be deleted from merge-base checking in write_grafts() -sub graft_tree_joins { - my $grafts = shift; - map_tree_joins() if (@_branch_from && !%tree_map); - return unless %tree_map; - - git_svn_each(sub { - my $i = shift; - my @args = (qw/rev-list --pretty=raw/, "refs/remotes/$i"); - my ($fh, $ctx) = command_output_pipe(@args); - while (<$fh>) { - next unless /^commit ($sha1)$/o; - my $c = $1; - my ($t) = (<$fh> =~ /^tree ($sha1)$/o); - next unless $tree_map{$t}; - - my $l; - do { - $l = readline $fh; - } until ($l =~ /^committer (?:.+) (\d+) ([\-\+]?\d+)$/); - - my ($s, $tz) = ($1, $2); - if ($tz =~ s/^\+//) { - $s += tz_to_s_offset($tz); - } elsif ($tz =~ s/^\-//) { - $s -= tz_to_s_offset($tz); - } - - my ($url_a, $r_a, $uuid_a) = cmt_metadata($c); - - foreach my $p (@{$tree_map{$t}}) { - next if $p eq $c; - my $mb = eval { command('merge-base', $c, $p) }; - next unless ($@ || $?); - if (defined $r_a) { - # see if SVN says it's a relative - my ($url_b, $r_b, $uuid_b) = - cmt_metadata($p); - next if (defined $url_b && - defined $url_a && - ($url_a eq $url_b) && - ($uuid_a eq $uuid_b)); - if ($uuid_a eq $uuid_b) { - if ($r_b < $r_a) { - $grafts->{$c}->{$p} = 2; - next; - } elsif ($r_b > $r_a) { - $grafts->{$p}->{$c} = 2; - next; - } - } - } - my $ct = get_commit_time($p); - if ($ct < $s) { - $grafts->{$c}->{$p} = 2; - } elsif ($ct > $s) { - $grafts->{$p}->{$c} = 2; - } - # what should we do when $ct == $s ? - } - } - command_close_pipe($fh, $ctx); - }); -} - -sub graft_file_copy_lib { - my ($grafts, $l_map, $u) = @_; - my $tree_paths = $l_map->{$u}; - my $pfx = common_prefix([keys %$tree_paths]); - my ($repo, $path) = repo_path_split($u.$pfx); - $SVN = Git::SVN::Ra->new($repo); - - my ($base, $head) = libsvn_parse_revision(); - my $inc = 1000; - my ($min, $max) = ($base, $head < $base+$inc ? $head : $base+$inc); - my $eh = $SVN::Error::handler; - $SVN::Error::handler = \&libsvn_skip_unknown_revs; - while (1) { - $SVN->dup->get_log([$path], $min, $max, 0, 2, 1, - sub { - libsvn_graft_file_copies($grafts, $tree_paths, - $path, @_); - }); - last if ($max >= $head); - $min = $max + 1; - $max += $inc; - $max = $head if ($max > $head); - } - $SVN::Error::handler = $eh; -} - -sub process_merge_msg_matches { - my ($grafts, $l_map, $u, $p, $c, @matches) = @_; - my (@strong, @weak); - foreach (@matches) { - # merging with ourselves is not interesting - next if $_ eq $p; - if ($l_map->{$u}->{$_}) { - push @strong, $_; - } else { - push @weak, $_; - } - } - foreach my $w (@weak) { - last if @strong; - # no exact match, use branch name as regexp. - my $re = qr/\Q$w\E/i; - foreach (keys %{$l_map->{$u}}) { - if (/$re/) { - push @strong, $l_map->{$u}->{$_}; - last; - } - } - last if @strong; - $w = basename($w); - $re = qr/\Q$w\E/i; - foreach (keys %{$l_map->{$u}}) { - if (/$re/) { - push @strong, $l_map->{$u}->{$_}; - last; - } - } - } - my ($rev) = ($c->{m} =~ /^git-svn-id:\s(?:\S+?)\@(\d+) - \s(?:[a-f\d\-]+)$/xsm); - unless (defined $rev) { - ($rev) = ($c->{m} =~/^git-svn-id:\s(\d+) - \@(?:[a-f\d\-]+)/xsm); - return unless defined $rev; - } - foreach my $m (@strong) { - my ($r0, $s0) = find_rev_before($rev, $m, 1); - $grafts->{$c->{c}}->{$s0} = 1 if defined $s0; - } -} - -sub graft_merge_msg { - my ($grafts, $l_map, $u, $p, @re) = @_; - - my $x = $l_map->{$u}->{$p}; - my $rl = rev_list_raw("refs/remotes/$x"); - while (my $c = next_rev_list_entry($rl)) { - foreach my $re (@re) { - my (@br) = ($c->{m} =~ /$re/g); - next unless @br; - process_merge_msg_matches($grafts,$l_map,$u,$p,$c,@br); - } - } -} - sub verify_ref { my ($ref) = @_; eval { command_oneline([ 'rev-parse', '--verify', $ref ], @@ -807,58 +610,6 @@ sub get_tree_from_treeish { return $expected; } -sub get_diff { - my ($from, $treeish) = @_; - print "diff-tree $from $treeish\n"; - my @diff_tree = qw(diff-tree -z -r); - if ($_cp_similarity) { - push @diff_tree, "-C$_cp_similarity"; - } else { - push @diff_tree, '-C'; - } - push @diff_tree, '--find-copies-harder' if $_find_copies_harder; - push @diff_tree, "-l$_l" if defined $_l; - push @diff_tree, $from, $treeish; - my ($diff_fh, $ctx) = command_output_pipe(@diff_tree); - local $/ = "\0"; - my $state = 'meta'; - my @mods; - while (<$diff_fh>) { - chomp $_; # this gets rid of the trailing "\0" - 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, - sha1_b => $3, chg => $4 }; - if ($4 =~ /^(?:C|R)$/) { - $state = 'file_a'; - } else { - $state = 'file_b'; - } - } elsif ($state eq 'file_a') { - my $x = $mods[$#mods] or croak "Empty array\n"; - if ($x->{chg} !~ /^(?:C|R)$/) { - croak "Error parsing $_, $x->{chg}\n"; - } - $x->{file_a} = $_; - $state = 'file_b'; - } elsif ($state eq 'file_b') { - my $x = $mods[$#mods] or croak "Empty array\n"; - if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) { - croak "Error parsing $_, $x->{chg}\n"; - } - if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) { - croak "Error parsing $_, $x->{chg}\n"; - } - $x->{file_b} = $_; - $state = 'meta'; - } else { - croak "Error parsing $_\n"; - } - } - command_close_pipe($diff_fh, $ctx); - return \@mods; -} - sub get_commit_entry { my ($treeish) = shift; my %log_entry = ( log => '', tree => get_tree_from_treeish($treeish) ); @@ -899,34 +650,6 @@ sub get_commit_entry { \%log_entry; } -sub rev_list_raw { - my ($fh, $c) = command_output_pipe(qw/rev-list --pretty=raw/, @_); - return { fh => $fh, ctx => $c, t => { } }; -} - -sub next_rev_list_entry { - my $rl = shift; - my $fh = $rl->{fh}; - my $x = $rl->{t}; - while (<$fh>) { - if (/^commit ($sha1)$/o) { - if ($x->{c}) { - $rl->{t} = { c => $1 }; - return $x; - } else { - $x->{c} = $1; - } - } elsif (/^parent ($sha1)$/o) { - $x->{p}->{$1} = 1; - } elsif (s/^ //) { - $x->{m} ||= ''; - $x->{m} .= $_; - } - } - command_close_pipe($fh, $rl->{ctx}); - return ($x != $rl->{t}) ? $x : undef; -} - sub s_to_file { my ($str, $file, $mode) = @_; open my $fd,'>',$file or croak $!; @@ -962,43 +685,6 @@ sub check_upgrade_needed { } } -# fills %tree_map with a reverse mapping of trees to commits. Useful -# for finding parents to commit on. -sub map_tree_joins { - my %seen; - foreach my $br (@_branch_from) { - my $pipe = command_output_pipe(qw/rev-list - --topo-order --pretty=raw/, $br); - while (<$pipe>) { - if (/^commit ($sha1)$/o) { - my $commit = $1; - - # if we've seen a commit, - # we've seen its parents - last if $seen{$commit}; - my ($tree) = (<$pipe> =~ /^tree ($sha1)$/o); - unless (defined $tree) { - die "Failed to parse commit $commit\n"; - } - push @{$tree_map{$tree}}, $commit; - $seen{$commit} = 1; - } - } - close $pipe; - } -} - -sub load_all_refs { - if (@_branch_from) { - print STDERR '--branch|-b parameters are ignored when ', - "--branch-all-refs|-B is passed\n"; - } - - # don't worry about rev-list on non-commit objects/tags, - # it shouldn't blow up if a ref is a blob or tree... - @_branch_from = command(qw/rev-parse --symbolic --all/); -} - # ' = real-name ' mapping based on git-svnimport: sub load_authors { open my $authors, '<', $_authors or die "Can't open $_authors $!\n"; @@ -1073,20 +759,6 @@ sub migration_check { print "Done upgrading.\n"; } -sub find_rev_before { - my ($r, $id, $eq_ok) = @_; - my $f = "$GIT_DIR/svn/$id/.rev_db"; - return (undef,undef) unless -r $f; - --$r unless $eq_ok; - while ($r > 0) { - if (my $c = revdb_get($f, $r)) { - return ($r, $c); - } - --$r; - } - return (undef, undef); -} - sub init_vars { $GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn'; $Git::SVN::default = $GIT_SVN; @@ -1094,7 +766,6 @@ sub init_vars { $REVDB = "$GIT_SVN_DIR/.rev_db"; $GIT_SVN_INDEX = "$GIT_SVN_DIR/index"; $SVN_URL = undef; - %tree_map = (); } # convert GetOpt::Long specs for use by git-config @@ -1120,95 +791,6 @@ sub read_repo_config { } } -sub set_default_vals { - if (defined $_repack) { - $_repack = 1000 if ($_repack <= 0); - $_repack_nr = $_repack; - $_repack_flags ||= '-d'; - } -} - -sub read_grafts { - my $gr_file = shift; - my ($grafts, $comments) = ({}, {}); - if (open my $fh, '<', $gr_file) { - my @tmp; - while (<$fh>) { - if (/^($sha1)\s+/) { - my $c = $1; - if (@tmp) { - @{$comments->{$c}} = @tmp; - @tmp = (); - } - foreach my $p (split /\s+/, $_) { - $grafts->{$c}->{$p} = 1; - } - } else { - push @tmp, $_; - } - } - close $fh or croak $!; - @{$comments->{'END'}} = @tmp if @tmp; - } - return ($grafts, $comments); -} - -sub write_grafts { - my ($grafts, $comments, $gr_file) = @_; - - open my $fh, '>', $gr_file or croak $!; - foreach my $c (sort keys %$grafts) { - if ($comments->{$c}) { - print $fh $_ foreach @{$comments->{$c}}; - } - my $p = $grafts->{$c}; - my %x; # real parents - delete $p->{$c}; # commits are not self-reproducing... - my $ch = command_output_pipe(qw/cat-file commit/, $c); - while (<$ch>) { - if (/^parent ($sha1)/) { - $x{$1} = $p->{$1} = 1; - } else { - last unless /^\S/; - } - } - close $ch; # breaking the pipe - - # if real parents are the only ones in the grafts, drop it - next if join(' ',sort keys %$p) eq join(' ',sort keys %x); - - my (@ip, @jp, $mb); - my %del = %x; - @ip = @jp = keys %$p; - foreach my $i (@ip) { - next if $del{$i} || $p->{$i} == 2; - foreach my $j (@jp) { - next if $i eq $j || $del{$j} || $p->{$j} == 2; - $mb = eval { command('merge-base', $i, $j) }; - next unless $mb; - chomp $mb; - next if $x{$mb}; - if ($mb eq $j) { - delete $p->{$i}; - $del{$i} = 1; - } elsif ($mb eq $i) { - delete $p->{$j}; - $del{$j} = 1; - } - } - } - - # if real parents are the only ones in the grafts, drop it - next if join(' ',sort keys %$p) eq join(' ',sort keys %x); - - print $fh $c, ' ', join(' ', sort keys %$p),"\n"; - } - if ($comments->{'END'}) { - print $fh $_ foreach @{$comments->{'END'}}; - } - close $fh or croak $!; -} - sub read_url_paths_all { my ($l_map, $pfx, $p) = @_; my @dir; @@ -1936,48 +1518,6 @@ sub uri_decode { $f } -sub libsvn_parse_revision { - my $base = shift; - my $head = $SVN->get_latest_revnum(); - if (!defined $_revision || $_revision eq 'BASE:HEAD') { - return ($base + 1, $head) if (defined $base); - return (0, $head); - } - return ($1, $2) if ($_revision =~ /^(\d+):(\d+)$/); - return ($_revision, $_revision) if ($_revision =~ /^\d+$/); - if ($_revision =~ /^BASE:(\d+)$/) { - return ($base + 1, $1) if (defined $base); - return (0, $head); - } - return ($1, $head) if ($_revision =~ /^(\d+):HEAD$/); - die "revision argument: $_revision not understood by git-svn\n", - "Try using the command-line svn client instead\n"; -} - -sub libsvn_traverse_ignore { - my ($fh, $path, $r) = @_; - $path =~ s#^/+##g; - my ($dirent, undef, $props) = $SVN->get_dir($path, $r); - my $p = $path; - $p =~ s#^\Q$SVN->{svn_path}\E/##; - print $fh length $p ? "\n# $p\n" : "\n# /\n"; - if (my $s = $props->{'svn:ignore'}) { - $s =~ s/[\r\n]+/\n/g; - chomp $s; - if (length $p == 0) { - $s =~ s#\n#\n/$p#g; - print $fh "/$s\n"; - } else { - $s =~ s#\n#\n/$p/#g; - print $fh "/$p/$s\n"; - } - } - foreach (sort keys %$dirent) { - next if $dirent->{$_}->kind != $SVN::Node::dir; - libsvn_traverse_ignore($fh, "$path/$_", $r); - } -} - sub revisions_eq { my ($path, $r0, $r1) = @_; return 1 if $r0 == $r1; @@ -2065,69 +1605,6 @@ sub _libsvn_new_tree { libsvn_log_entry($rev, $author, $date, $log, $parents, $ed); } -sub find_graft_path_commit { - my ($tree_paths, $p1, $r1) = @_; - foreach my $x (keys %$tree_paths) { - next unless ($p1 =~ /^\Q$x\E/); - my $i = $tree_paths->{$x}; - my ($r0, $parent) = find_rev_before($r1,$i,1); - return $parent if (defined $r0 && $r0 == $r1); - print STDERR "r$r1 of $i not imported\n"; - next; - } - return undef; -} - -sub find_graft_path_parents { - my ($grafts, $tree_paths, $c, $p0, $r0) = @_; - foreach my $x (keys %$tree_paths) { - next unless ($p0 =~ /^\Q$x\E/); - my $i = $tree_paths->{$x}; - my ($r, $parent) = find_rev_before($r0, $i, 1); - if (defined $r && defined $parent && revisions_eq($x,$r,$r0)) { - my ($url_b, undef, $uuid_b) = cmt_metadata($c); - my ($url_a, undef, $uuid_a) = cmt_metadata($parent); - next if ($url_a && $url_b && $url_a eq $url_b && - $uuid_b eq $uuid_a); - $grafts->{$c}->{$parent} = 1; - } - } -} - -sub libsvn_graft_file_copies { - my ($grafts, $tree_paths, $path, $paths, $rev) = @_; - foreach (keys %$paths) { - my $i = $paths->{$_}; - my ($m, $p0, $r0) = ($i->action, $i->copyfrom_path, - $i->copyfrom_rev); - next unless (defined $p0 && defined $r0); - - my $p1 = $_; - $p1 =~ s#^/##; - $p0 =~ s#^/##; - my $c = find_graft_path_commit($tree_paths, $p1, $rev); - next unless $c; - find_graft_path_parents($grafts, $tree_paths, $c, $p0, $r0); - } -} - -sub revdb_get { - my ($file, $rev) = @_; - my $ret; - my $offset = $rev * 41; - open my $fh, '<', $file or croak $!; - seek $fh, $offset, 0; - if (tell $fh == $offset) { - $ret = readline $fh; - if (defined $ret) { - chomp $ret; - $ret = undef if ($ret =~ /^0{40}$/); - } - } - close $fh or croak $!; - return $ret; -} - { my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file. $SVN::Node::dir.$SVN::Node::unknown. diff --git a/t/t9103-git-svn-graft-branches.sh b/t/t9103-git-svn-graft-branches.sh deleted file mode 100755 index 8d946d2aa5..0000000000 --- a/t/t9103-git-svn-graft-branches.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/bin/sh -test_description='git-svn graft-branches' -. ./lib-git-svn.sh - -svnrepo="$svnrepo/test-git-svn" - -test_expect_success 'initialize repo' " - mkdir import && - cd import && - mkdir -p trunk branches tags && - echo hello > trunk/readme && - svn import -m 'import for git-svn' . $svnrepo && - cd .. && - svn cp -m 'tag a' $svnrepo/trunk $svnrepo/tags/a && - svn cp -m 'branch a' $svnrepo/trunk $svnrepo/branches/a && - svn co $svnrepo wc && - cd wc && - echo feedme >> branches/a/readme && - poke branches/a/readme && - svn commit -m hungry && - cd trunk && - svn merge -r3:4 $svnrepo/branches/a && - svn commit -m 'merge with a' && - cd ../.. && - git-svn multi-init $svnrepo -T trunk -b branches -t tags && - git-svn multi-fetch - " - -test_expect_success 'multi-init set .git/config correctly' " - test '$svnrepo/trunk' = '`git repo-config --get svn.trunk`' && - test '$svnrepo/branches' = '`git repo-config --get svn.branches`' && - test '$svnrepo/tags' = '`git repo-config --get svn.tags`' - " - -r1=`git-rev-list remotes/trunk | tail -n1` -r2=`git-rev-list remotes/tags/a | tail -n1` -r3=`git-rev-list remotes/a | tail -n1` -r4=`git-rev-parse remotes/a` -r5=`git-rev-parse remotes/trunk` - -test_expect_success 'test graft-branches regexes and copies' " - test -n "$r1" && - test -n "$r2" && - test -n "$r3" && - test -n "$r4" && - test -n "$r5" && - git-svn graft-branches && - grep '^$r2 $r1' $GIT_DIR/info/grafts && - grep '^$r3 $r1' $GIT_DIR/info/grafts && - grep '^$r5 ' $GIT_DIR/info/grafts | grep '$r4' | grep '$r1' - " - -test_debug 'gitk --all & sleep 1' - -test_expect_success 'test graft-branches with tree-joins' " - rm $GIT_DIR/info/grafts && - git-svn graft-branches --no-default-regex --no-graft-copy -B && - grep '^$r3 ' $GIT_DIR/info/grafts | grep '$r1' | grep '$r2' && - grep '^$r2 $r1' $GIT_DIR/info/grafts && - grep '^$r5 ' $GIT_DIR/info/grafts | grep '$r1' | grep '$r4' - " - -# the result of this is kinda funky, we have a strange history and -# this is just a test :) -test_debug 'gitk --all &' - -test_done From 706587fc6d56db1ba6c7207d4c0c456bac6f77c2 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 18 Jan 2007 17:50:01 -0800 Subject: [PATCH 081/201] git-svn: add support for metadata in .git/config Of course, we handle metadata migrations from previous versions and we have added unit tests. The new .git/config remotes resemble non-SVN remotes. Below is an example with comments: [svn-remote "git-svn"] ; like non-svn remotes, we have one URL per-remote url = http://foo.bar.org/svn ; 'fetch' keys are done in the same way as non-svn ; remotes, too. With the left-hand-side of the ':' ; being the remote (SVN) repository path relative to the ; above 'url' key; and the right-hand-side being a ; remote ref in git (refs/remotes/*). ; An empty left-hand-side means that it will fetch ; the entire contents of the 'url' key. ; old-style (migrated from previous versions of git-svn) ; are like this: fetch = :refs/remotes/git-svn ; this is created by a current version of git-svn ; using the multi-init command with an explicit ; url (specified above). This allows multi-init ; to reuse SVN::Ra connections. fetch = trunk:refs/remotes/trunk fetch = branches/a:refs/remotes/a fetch = branches/b:refs/remotes/b fetch = tags/0.1:refs/remotes/tags/0.1 fetch = tags/0.2:refs/remotes/tags/0.2 fetch = tags/0.3:refs/remotes/tags/0.3 [svn-remote "alt"] ; this is another old-style remote migrated over ; to the new config format url = http://foo.bar.org/alt fetch = :refs/remotes/alt Signed-off-by: Eric Wong --- git-svn.perl | 583 +++++++++++++++++++------------------ t/t9107-git-svn-migrate.sh | 63 ++++ 2 files changed, 367 insertions(+), 279 deletions(-) create mode 100755 t/t9107-git-svn-migrate.sh diff --git a/git-svn.perl b/git-svn.perl index e75021bce2..9d50d305c1 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -13,9 +13,8 @@ $AUTHOR = 'Eric Wong '; $VERSION = '@@GIT_VERSION@@'; -use Cwd qw/abs_path/; -$GIT_DIR = abs_path($ENV{GIT_DIR} || '.git'); -$ENV{GIT_DIR} = $GIT_DIR; +$ENV{GIT_DIR} ||= '.git'; +$Git::SVN::default_repo_id = $ENV{GIT_SVN_ID} || 'git-svn'; my $LC_ALL = $ENV{LC_ALL}; $Git::SVN::Log::TZ = $ENV{TZ}; @@ -47,6 +46,7 @@ BEGIN foreach (qw/command command_oneline command_noisy command_output_pipe command_input_pipe command_close_pipe/) { $s .= "*SVN::Git::Editor::$_ = *SVN::Git::Fetcher::$_ = ". + "*Git::SVN::Migration::$_ = ". "*Git::SVN::Log::$_ = *Git::SVN::$_ = *$_ = *Git::$_; "; } eval $s; @@ -64,17 +64,17 @@ BEGIN $_version, $_upgrade, $_merge, $_strategy, $_dry_run, $_prefix); -my @repo_path_split_cache; +my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username, + 'config-dir=s' => \$Git::SVN::Ra::config_dir, + 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache ); my %fc_opts = ( 'follow-parent|follow' => \$_follow_parent, 'authors-file|A=s' => \$_authors, 'repack:i' => \$_repack, 'no-metadata' => \$_no_metadata, 'quiet|q' => \$_q, - 'username=s' => \$Git::SVN::Prompt::_username, - 'config-dir=s' => \$Git::SVN::Ra::config_dir, - 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache, - 'repack-flags|repack-args|repack-opts=s' => \$_repack_flags); + 'repack-flags|repack-args|repack-opts=s' => \$_repack_flags, + %remote_opts ); my ($_trunk, $_tags, $_branches); my %multi_opts = ( 'trunk|T=s' => \$_trunk, @@ -110,16 +110,20 @@ BEGIN 'upgrade' => \$_upgrade } ], 'multi-init' => [ \&cmd_multi_init, 'Initialize multiple trees (like git-svnimport)', - { %multi_opts, %init_opts, + { %multi_opts, %init_opts, %remote_opts, 'revision|r=i' => \$_revision, - 'username=s' => \$Git::SVN::Prompt::_username, - 'config-dir=s' => \$Git::SVN::Ra::config_dir, - 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache, 'prefix=s' => \$_prefix, } ], 'multi-fetch' => [ \&cmd_multi_fetch, 'Fetch multiple trees (like git-svnimport)', \%fc_opts ], + 'migrate' => [ sub { }, + # no-op, we automatically run this anyways, + # we may add a flag to automatically optimize the + # configuration to avoid reconnects in the future + 'Migrate configuration/metadata/layout from + previous versions of git-svn', + \%remote_opts ], 'log' => [ \&Git::SVN::Log::cmd_show_log, 'Show commit logs', { 'limit=i' => \$Git::SVN::Log::limit, 'revision|r=s' => \$_revision, @@ -154,15 +158,16 @@ BEGIN read_repo_config(\%opts); my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version, - 'id|i=s' => \$GIT_SVN); + 'id|i=s' => \$Git::SVN::default_repo_id); exit 1 if (!$rv && $cmd ne 'log'); usage(0) if $_help; version() if $_version; usage(1) unless defined $cmd; -init_vars(); load_authors() if $_authors; -migration_check() unless $cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/; +unless ($cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/) { + Git::SVN::Migration::migration_check(); +} $cmd{$cmd}->[0]->(@ARGV); exit 0; @@ -203,17 +208,12 @@ sub version { sub cmd_rebuild { my $url = shift; - my $gs = $url ? Git::SVN->init(undef, $url) + my $gs = $url ? Git::SVN->init($url) : eval { Git::SVN->new }; $gs ||= Git::SVN->_new; if (!verify_ref($gs->refname.'^0')) { $gs->copy_remote_ref; } - if ($_upgrade) { - command_noisy('update-ref',$gs->refname, $gs->{id}.'-HEAD'); - } else { - $gs->check_upgrade_needed; - } my ($rev_list, $ctx) = command_output_pipe("rev-list", $gs->refname); my $latest; @@ -238,7 +238,7 @@ sub cmd_rebuild { if (!$gs->{url} && !$url) { fatal "SVN repository location required\n"; } - $gs = Git::SVN->init(undef, $url); + $gs = Git::SVN->init($url); $latest = $rev; } $gs->rev_db_set($rev, $c); @@ -268,7 +268,7 @@ sub cmd_init { } do_git_init_db(); - Git::SVN->init(undef, $url); + Git::SVN->init($url); } sub cmd_fetch { @@ -389,28 +389,35 @@ sub cmd_multi_init { } do_git_init_db(); $_prefix = '' unless defined $_prefix; + $url =~ s#/+$## if defined $url; if (defined $_trunk) { - my $gs_trunk = eval { Git::SVN->new($_prefix . 'trunk') }; + my $trunk_ref = $_prefix . 'trunk'; + # try both old-style and new-style lookups: + my $gs_trunk = eval { Git::SVN->new($trunk_ref) }; unless ($gs_trunk) { - my $trunk_url = complete_svn_url($url, $_trunk); - $gs_trunk = Git::SVN->init($_prefix . 'trunk', - $trunk_url); - command_noisy('config', 'svn.trunk', $trunk_url); + my ($trunk_url, $trunk_path) = + complete_svn_url($url, $_trunk); + $gs_trunk = Git::SVN->init($trunk_url, $trunk_path, + undef, $trunk_ref); } } + return unless defined $_branches || defined $_tags; my $ra = $url ? Git::SVN::Ra->new($url) : undef; complete_url_ls_init($ra, $_branches, '--branches/-b', $_prefix); complete_url_ls_init($ra, $_tags, '--tags/-t', $_prefix . 'tags/'); } sub cmd_multi_fetch { - # try to do trunk first, since branches/tags - # may be descended from it. - if (-e "$ENV{GIT_DIR}/svn/trunk/info/url") { - my $gs = Git::SVN->new('trunk'); - $gs->fetch(@_); + my @gs; + foreach (command(qw/config -l/)) { + next unless m!^svn-remote\.(.+)\.fetch= + \s*(.*)\s*:\s*refs/remotes/(.+)\s*$!x; + my ($repo_id, $path, $ref_id) = ($1, $2, $3); + push @gs, Git::SVN->new($ref_id, $repo_id, $path); + } + foreach (@gs) { + $_->fetch; } - rec_fetch('', "$ENV{GIT_DIR}/svn", @_); } # this command is special because it requires no metadata @@ -464,95 +471,50 @@ sub cmd_commit_diff { ########################### utility functions ######################### -sub rec_fetch { - my ($pfx, $p, @args) = @_; - my @dir; - foreach (sort <$p/*>) { - if (-r "$_/info/url") { - $pfx .= '/' if $pfx && $pfx !~ m!/$!; - my $id = $pfx . basename $_; - next if $id eq 'trunk'; - my $gs = Git::SVN->new($id); - $gs->fetch(@args); - } elsif (-d $_) { - push @dir, $_; - } - } - foreach (@dir) { - my $x = $_; - $x =~ s!^\Q$ENV{GIT_DIR}\E/svn/!!o; - rec_fetch($x, $_, @args); - } -} - sub complete_svn_url { my ($url, $path) = @_; $path =~ s#/+$##; - $url =~ s#/+$## if $url; if ($path !~ m#^[a-z\+]+://#) { - $path = '/' . $path if ($path !~ m#^/#); if (!defined $url || $url !~ m#^[a-z\+]+://#) { fatal("E: '$path' is not a complete URL ", "and a separate URL is not specified\n"); } - $path = $url . $path; + return ($url, $path); } - return $path; + return ($path, ''); } sub complete_url_ls_init { - my ($ra, $path, $switch, $pfx) = @_; - unless ($path) { + my ($ra, $repo_path, $switch, $pfx) = @_; + unless ($repo_path) { print STDERR "W: $switch not specified\n"; return; } - $path =~ s#/+$##; - if ($path =~ m#^[a-z\+]+://#) { - $ra = Git::SVN::Ra->new($path); - $path = ''; + $repo_path =~ s#/+$##; + if ($repo_path =~ m#^[a-z\+]+://#) { + $ra = Git::SVN::Ra->new($repo_path); + $repo_path = ''; } else { - $path =~ s#^/+##; + $repo_path =~ s#^/+##; unless ($ra) { - fatal("E: '$path' is not a complete URL ", + fatal("E: '$repo_path' is not a complete URL ", "and a separate URL is not specified\n"); } } my $r = defined $_revision ? $_revision : $ra->get_latest_revnum; - my ($dirent, undef, undef) = $ra->get_dir($path, $r); - my $url = $ra->{url} . (length $path ? "/$path" : ''); + my ($dirent, undef, undef) = $ra->get_dir($repo_path, $r); + my $url = $ra->{url}; foreach my $d (sort keys %$dirent) { next if ($dirent->{$d}->kind != $SVN::Node::dir); - my $u = "$url/$d"; - my $id = "$pfx$d"; - my $gs = eval { Git::SVN->new($id) }; + my $path = "$repo_path/$d"; + my $ref = "$pfx$d"; + my $gs = eval { Git::SVN->new($ref) }; # don't try to init already existing refs unless ($gs) { - print "init $u => $id\n"; - Git::SVN->init($id, $u); + print "init $url/$path => $ref\n"; + Git::SVN->init($url, $path, undef, $ref); } } - my ($n) = ($switch =~ /^--(\w+)/); - command_noisy('config', "svn.$n", $url); -} - -sub common_prefix { - my $paths = shift; - my %common; - foreach (@$paths) { - my @tmp = split m#/#, $_; - my $p = ''; - while (my $x = shift @tmp) { - $p .= "/$x"; - $common{$p} ||= 0; - $common{$p}++; - } - } - foreach (sort {length $b <=> length $a} keys %common) { - if ($common{$_} == @$paths) { - return $_; - } - } - return ''; } sub verify_ref { @@ -561,34 +523,6 @@ sub verify_ref { { STDERR => 0 }); }; } -sub repo_path_split { - my $full_url = shift; - $full_url =~ s#/+$##; - - foreach (@repo_path_split_cache) { - if ($full_url =~ s#$_##) { - my $u = $1; - $full_url =~ s#^/+##; - return ($u, $full_url); - } - } - my $tmp = Git::SVN::Ra->new($full_url); - return ($tmp->{repos_root}, $tmp->{svn_path}); -} - -sub setup_git_svn { - defined $SVN_URL or croak "SVN repository location required\n"; - unless (-d $GIT_DIR) { - croak "GIT_DIR=$GIT_DIR does not exist!\n"; - } - mkpath([$GIT_SVN_DIR]); - mkpath(["$GIT_SVN_DIR/info"]); - open my $fh, '>>',$REVDB or croak $!; - close $fh; - s_to_file($SVN_URL,"$GIT_SVN_DIR/info/url"); - -} - sub get_tree_from_treeish { my ($treeish) = @_; # $treeish can be a symbolic ref, too: @@ -668,23 +602,6 @@ sub file_to_s { return $ret; } -sub check_upgrade_needed { - if (!-r $REVDB) { - -d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]); - open my $fh, '>>',$REVDB or croak $!; - close $fh; - } - return unless eval { - command([qw/rev-parse --verify/,"$GIT_SVN-HEAD^0"], - {STDERR => 0}); - }; - my $head = eval { command('rev-parse',"refs/remotes/$GIT_SVN") }; - if ($@ || !$head) { - print STDERR "Please run: $0 rebuild --upgrade\n"; - exit 1; - } -} - # ' = real-name ' mapping based on git-svnimport: sub load_authors { open my $authors, '<', $_authors or die "Can't open $_authors $!\n"; @@ -702,75 +619,9 @@ sub load_authors { close $authors or croak $!; } -sub git_svn_each { - my $sub = shift; - foreach (command(qw/rev-parse --symbolic --all/)) { - next unless s#^refs/remotes/##; - chomp $_; - next unless -f "$GIT_DIR/svn/$_/info/url"; - &$sub($_); - } -} - -sub migrate_revdb { - git_svn_each(sub { - my $id = shift; - defined(my $pid = fork) or croak $!; - if (!$pid) { - $GIT_SVN = $ENV{GIT_SVN_ID} = $id; - init_vars(); - exit 0 if -r $REVDB; - print "Upgrading svn => git mapping...\n"; - -d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]); - open my $fh, '>>',$REVDB or croak $!; - close $fh; - rebuild(); - print "Done upgrading. You may now delete the ", - "deprecated $GIT_SVN_DIR/revs directory\n"; - exit 0; - } - waitpid $pid, 0; - croak $? if $?; - }); -} - -sub migration_check { - migrate_revdb() unless (-e $REVDB); - return if (-d "$GIT_DIR/svn" || !-d $GIT_DIR); - print "Upgrading repository...\n"; - unless (-d "$GIT_DIR/svn") { - mkdir "$GIT_DIR/svn" or croak $!; - } - print "Data from a previous version of git-svn exists, but\n\t", - "$GIT_SVN_DIR\n\t(required for this version ", - "($VERSION) of git-svn) does not.\n"; - - foreach my $x (command(qw/rev-parse --symbolic --all/)) { - next unless $x =~ s#^refs/remotes/##; - chomp $x; - next unless -f "$GIT_DIR/$x/info/url"; - my $u = eval { file_to_s("$GIT_DIR/$x/info/url") }; - next unless $u; - my $dn = dirname("$GIT_DIR/svn/$x"); - mkpath([$dn]) unless -d $dn; - rename "$GIT_DIR/$x", "$GIT_DIR/svn/$x" or croak "$!: $x"; - } - migrate_revdb() if (-d $GIT_SVN_DIR && !-w $REVDB); - print "Done upgrading.\n"; -} - -sub init_vars { - $GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn'; - $Git::SVN::default = $GIT_SVN; - $GIT_SVN_DIR = "$GIT_DIR/svn/$GIT_SVN"; - $REVDB = "$GIT_SVN_DIR/.rev_db"; - $GIT_SVN_INDEX = "$GIT_SVN_DIR/index"; - $SVN_URL = undef; -} - # convert GetOpt::Long specs for use by git-config sub read_repo_config { - return unless -d $GIT_DIR; + return unless -d $ENV{GIT_DIR}; my $opts = shift; foreach my $o (keys %$opts) { my $v = $opts->{$o}; @@ -791,38 +642,6 @@ sub read_repo_config { } } -sub read_url_paths_all { - my ($l_map, $pfx, $p) = @_; - my @dir; - foreach (<$p/*>) { - if (-r "$_/info/url") { - $pfx .= '/' if $pfx && $pfx !~ m!/$!; - my $id = $pfx . basename $_; - my $url = file_to_s("$_/info/url"); - my ($u, $p) = repo_path_split($url); - $l_map->{$u}->{$p} = $id; - } elsif (-d $_) { - push @dir, $_; - } - } - foreach (@dir) { - my $x = $_; - $x =~ s!^\Q$GIT_DIR\E/svn/!!o; - read_url_paths_all($l_map, $x, $_); - } -} - -# this one only gets ids that have been imported, not new ones -sub read_url_paths { - my $l_map = {}; - git_svn_each(sub { my $x = shift; - my $url = file_to_s("$GIT_DIR/svn/$x/info/url"); - my ($u, $p) = repo_path_split($url); - $l_map->{$u}->{$p} = $x; - }); - return $l_map; -} - sub extract_metadata { my $id = shift or return (undef, undef, undef); my ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\s(\S+?)\@(\d+) @@ -866,7 +685,7 @@ sub tz_to_s_offset { package Git::SVN; use strict; use warnings; -use vars qw/$default/; +use vars qw/$default_repo_id/; use Carp qw/croak/; use File::Path qw/mkpath/; use IPC::Open3; @@ -882,28 +701,76 @@ BEGIN svn:entry:committed-date/; } +# we allow dashes, unlike remotes2config.sh +sub sanitize_remote_name { + my ($name) = @_; + $name =~ tr/A-Za-z0-9-/./c; + $name; +} + sub init { - my ($class, $id, $url) = @_; - my $self = _new($class, $id); - mkpath(["$self->{dir}/info"]); + my ($class, $url, $path, $repo_id, $ref_id) = @_; + my $self = _new($class, $repo_id, $ref_id, $path); + mkpath([$self->{dir}]); if (defined $url) { $url =~ s!/+$!!; # strip trailing slash - ::s_to_file($url, "$self->{dir}/info/url"); + my $orig_url = eval { + command_oneline('config', '--get', + "svn-remote.$repo_id.url") + }; + if ($orig_url) { + if ($orig_url ne $url) { + die "svn-remote.$repo_id.url already set: ", + "$orig_url\nwanted to set to: $url\n"; + } + } else { + command_noisy('config', + "svn-remote.$repo_id.url", $url); + } + command_noisy('config', '--add', + "svn-remote.$repo_id.fetch", + "$path:".$self->refname); } $self->{url} = $url; - open my $fh, '>>', $self->{db_path} or croak $!; - close $fh or croak $!; + unless (-f $self->{db_path}) { + open my $fh, '>>', $self->{db_path} or croak $!; + close $fh or croak $!; + } $self; } +sub find_ref { + my ($ref_id) = @_; + foreach (command(qw/config -l/)) { + next unless m!^svn-remote\.(.+)\.fetch= + \s*(.*)\s*:\s*refs/remotes/(.+)\s*$!x; + my ($repo_id, $path, $ref) = ($1, $2, $3); + if ($ref eq $ref_id) { + $path = '' if ($path =~ m#^\./?#); + return ($repo_id, $path); + } + } + (undef, undef, undef); +} + sub new { - my ($class, $id) = @_; - my $self = _new($class, $id); - $self->{url} = ::file_to_s("$self->{dir}/info/url"); + my ($class, $ref_id, $repo_id, $path) = @_; + if (defined $ref_id && !defined $repo_id && !defined $path) { + ($repo_id, $path) = find_ref($ref_id); + if (!defined $repo_id) { + die "Could not find a \"svn-remote.*.fetch\" key ", + "in the repository configuration matching: ", + "refs/remotes/$ref_id\n"; + } + } + my $self = _new($class, $repo_id, $ref_id, $path); + $self->{url} = command_oneline('config', '--get', + "svn-remote.$repo_id.url") or + die "Failed to read \"svn-remote.$repo_id.url\" in config\n"; $self; } -sub refname { "refs/remotes/$_[0]->{id}" } +sub refname { "refs/remotes/$_[0]->{ref_id}" } sub ra { my ($self) = shift; @@ -952,7 +819,7 @@ sub last_rev_commit { return ($self->{last_rev}, $self->{last_commit}); } my $c = ::verify_ref($self->refname.'^0'); - if (defined $c && length $c) { + if ($c) { my $rev = (::cmt_metadata($c))[1]; if (defined $rev) { ($self->{last_rev}, $self->{last_commit}) = ($rev, $c); @@ -1064,18 +931,9 @@ sub get_commit_parents { @ret; } -sub check_upgrade_needed { +sub full_url { my ($self) = @_; - if (!-r $self->{db_path}) { - -d $self->{dir} or mkpath([$self->{dir}]); - open my $fh, '>>', $self->{db_path} or croak $!; - close $fh; - } - return unless ::verify_ref($self->{id}.'-HEAD^0'); - my $head = ::verify_ref($self->refname.'^0'); - if ($@ || !$head) { - ::fatal("Please run: $0 rebuild --upgrade\n"); - } + $self->ra->{url} . (length $self->{path} ? '/' . $self->{path} : ''); } sub do_git_commit { @@ -1105,7 +963,7 @@ sub do_git_commit { defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec)) or croak $!; print $msg_fh $log_entry->{log} or croak $!; - print $msg_fh "\ngit-svn-id: ", $self->ra->{url}, '@', + print $msg_fh "\ngit-svn-id: ", $self->full_url, '@', $log_entry->{revision}, ' ', $self->ra->uuid, "\n" or croak $!; $msg_fh->flush == 0 or croak $!; @@ -1128,7 +986,7 @@ sub do_git_commit { } sub do_fetch { - my ($self, $paths, $rev) = @_; #, $author, $date, $log) = @_; + my ($self, $paths, $rev) = @_; my $ed = SVN::Git::Fetcher->new($self); my ($last_rev, @parents); if ($self->{last_commit}) { @@ -1138,7 +996,8 @@ sub do_fetch { } else { $last_rev = $rev; } - unless ($self->ra->gs_do_update($last_rev, $rev, '', 1, $ed)) { + unless ($self->ra->gs_do_update($last_rev, $rev, + $self->{path}, 1, $ed)) { die "SVN connection failed somewhere...\n"; } $self->make_log_entry($rev, \@parents, $ed); @@ -1361,11 +1220,19 @@ sub rev_db_get { } sub _new { - my ($class, $id) = @_; - $id ||= $Git::SVN::default; - my $dir = "$ENV{GIT_DIR}/svn/$id"; - bless { id => $id, dir => $dir, index => "$dir/index", - db_path => "$dir/.rev_db" }, $class; + my ($class, $repo_id, $ref_id, $path) = @_; + unless (defined $repo_id && length $repo_id) { + $repo_id = $Git::SVN::default_repo_id; + } + unless (defined $ref_id && length $ref_id) { + $_[2] = $ref_id = $repo_id; + } + $_[1] = $repo_id = sanitize_remote_name($repo_id); + my $dir = "$ENV{GIT_DIR}/svn/$ref_id"; + $_[3] = $path = '' unless (defined $path); + bless { ref_id => $ref_id, dir => $dir, index => "$dir/index", + path => $path, + db_path => "$dir/.rev_db", repo_id => $repo_id }, $class; } sub uri_encode { @@ -1630,6 +1497,9 @@ sub new { my $self = SVN::Delta::Editor->new; bless $self, $class; $self->{c} = $git_svn->{last_commit} if exists $git_svn->{last_commit}; + if (length $git_svn->{path}) { + $self->{path_strip} = qr/\Q$git_svn->{path}\E\/?/; + } $self->{empty} = {}; $self->{dir_prop} = {}; $self->{file_prop} = {}; @@ -1650,33 +1520,41 @@ sub open_directory { { path => $path }; } +sub git_path { + my ($self, $path) = @_; + $path =~ s!$self->{path_strip}!! if $self->{path_strip}; + $path; +} + sub delete_entry { my ($self, $path, $rev, $pb) = @_; my $gui = $self->{gui}; + my $gpath = $self->git_path($path); # remove entire directories. - if (command('ls-tree', $self->{c}, '--', $path) =~ /^040000 tree/) { + if (command('ls-tree', $self->{c}, '--', $gpath) =~ /^040000 tree/) { my ($ls, $ctx) = command_output_pipe(qw/ls-tree -r --name-only -z/, - $self->{c}, '--', $path); + $self->{c}, '--', $gpath); local $/ = "\0"; while (<$ls>) { print $gui '0 ',0 x 40,"\t",$_ or croak $!; print "\tD\t$_\n" unless $self->{q}; } - print "\tD\t$path/\n" unless $self->{q}; + print "\tD\t$gpath/\n" unless $self->{q}; command_close_pipe($ls, $ctx); $self->{empty}->{$path} = 0 } else { - print $gui '0 ',0 x 40,"\t",$path,"\0" or croak $!; - print "\tD\t$path\n" unless $self->{q}; + print $gui '0 ',0 x 40,"\t",$gpath,"\0" or croak $!; + print "\tD\t$gpath\n" unless $self->{q}; } undef; } sub open_file { my ($self, $path, $pb, $rev) = @_; - my ($mode, $blob) = (command('ls-tree', $self->{c}, '--',$path) + my $gpath = $self->git_path($path); + my ($mode, $blob) = (command('ls-tree', $self->{c}, '--', $gpath) =~ /^(\d{6}) blob ([a-f\d]{40})\t/); unless (defined $mode && defined $blob) { die "$path was not found in commit $self->{c} (r$rev)\n"; @@ -1775,7 +1653,7 @@ sub apply_textdelta { sub close_file { my ($self, $fb, $exp) = @_; my $hash; - my $path = $fb->{path}; + my $path = $self->git_path($fb->{path}); if (my $fh = $fb->{fh}) { seek($fh, 0, 0) or croak $!; my $md5 = Digest::MD5->new; @@ -2223,7 +2101,7 @@ sub gs_do_update { $editor, $pool); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); my $new = ($rev_a == $rev_b); - $reporter->set_path($path, $rev_a, $new, @lock, $pool); + $reporter->set_path('', $rev_a, $new, @lock, $pool); $reporter->finish_report($pool); $pool->clear; $editor->{git_commit_ok}; @@ -2561,6 +2439,153 @@ sub cmd_show_log { print '-' x72,"\n" unless $incremental || $oneline; } +package Git::SVN::Migration; +# these version numbers do NOT correspond to actual version numbers +# of git nor git-svn. They are just relative. +# +# v0 layout: .git/$id/info/url, refs/heads/$id-HEAD +# +# v1 layout: .git/$id/info/url, refs/remotes/$id +# +# v2 layout: .git/svn/$id/info/url, refs/remotes/$id +# +# v3 layout: .git/svn/$id, refs/remotes/$id +# - info/url may remain for backwards compatibility +# - this is what we migrate up to this layout automatically, +# - this will be used by git svn init on single branches +# +# v4 layout: .git/svn/$repo_id/$id, refs/remotes/$repo_id/$id +# - this is only created for newly multi-init-ed +# repositories. Similar in spirit to the +# --use-separate-remotes option in git-clone (now default) +# - we do not automatically migrate to this (following +# the example set by core git) +use strict; +use warnings; +use Carp qw/croak/; +use File::Path qw/mkpath/; +use File::Basename qw/dirname/; + +sub migrate_from_v0 { + my $git_dir = $ENV{GIT_DIR}; + return undef unless -d $git_dir; + my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/); + my $migrated = 0; + while (<$fh>) { + chomp; + my ($id, $orig_ref) = ($_, $_); + next unless $id =~ s#^refs/heads/(.+)-HEAD$#$1#; + next unless -f "$git_dir/$id/info/url"; + my $new_ref = "refs/remotes/$id"; + if (::verify_ref("$new_ref^0")) { + print STDERR "W: $orig_ref is probably an old ", + "branch used by an ancient version of ", + "git-svn.\n", + "However, $new_ref also exists.\n", + "We will not be able ", + "to use this branch until this ", + "ambiguity is resolved.\n"; + next; + } + print STDERR "Migrating from v0 layout...\n" if !$migrated; + print STDERR "Renaming ref: $orig_ref => $new_ref\n"; + command_noisy('update-ref', $new_ref, $orig_ref); + command_noisy('update-ref', '-d', $orig_ref, $orig_ref); + $migrated++; + } + command_close_pipe($fh, $ctx); + print STDERR "Done migrating from v0 layout...\n" if $migrated; + $migrated; +} + +sub migrate_from_v1 { + my $git_dir = $ENV{GIT_DIR}; + my $migrated = 0; + return $migrated unless -d $git_dir; + my $svn_dir = "$git_dir/svn"; + + # just in case somebody used 'svn' as their $id at some point... + return $migrated if -d $svn_dir && ! -f "$svn_dir/info/url"; + + print STDERR "Migrating from a git-svn v1 layout...\n"; + mkpath([$svn_dir]); + print STDERR "Data from a previous version of git-svn exists, but\n\t", + "$svn_dir\n\t(required for this version ", + "($::VERSION) of git-svn) does not. exist\n"; + my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/); + while (<$fh>) { + my $x = $_; + next unless $x =~ s#^refs/remotes/##; + chomp $x; + next unless -f "$git_dir/$x/info/url"; + my $u = eval { ::file_to_s("$git_dir/$x/info/url") }; + next unless $u; + my $dn = dirname("$git_dir/svn/$x"); + mkpath([$dn]) unless -d $dn; + if ($x eq 'svn') { # they used 'svn' as GIT_SVN_ID: + mkpath(["$git_dir/svn/svn"]); + print STDERR " - $git_dir/$x/info => ", + "$git_dir/svn/$x/info\n"; + rename "$git_dir/$x/info", "$git_dir/svn/$x/info" or + croak "$!: $x"; + # don't worry too much about these, they probably + # don't exist with repos this old (save for index, + # and we can easily regenerate that) + foreach my $f (qw/unhandled.log index .rev_db/) { + rename "$git_dir/$x/$f", "$git_dir/svn/$x/$f"; + } + } else { + print STDERR " - $git_dir/$x => $git_dir/svn/$x\n"; + rename "$git_dir/$x", "$git_dir/svn/$x" or + croak "$!: $x"; + } + $migrated++; + } + command_close_pipe($fh, $ctx); + print STDERR "Done migrating from a git-svn v1 layout\n"; + $migrated; +} + +sub read_old_urls { + my ($l_map, $pfx, $path) = @_; + my @dir; + foreach (<$path/*>) { + if (-r "$_/info/url") { + $pfx .= '/' if $pfx && $pfx !~ m!/$!; + my $ref_id = $pfx . basename $_; + my $url = ::file_to_s("$_/info/url"); + $l_map->{$ref_id} = $url; + } elsif (-d $_) { + push @dir, $_; + } + } + foreach (@dir) { + my $x = $_; + $x =~ s!^\Q$ENV{GIT_DIR}\E/svn/!!o; + read_old_urls($l_map, $x, $_); + } +} + +sub migrate_from_v2 { + my @cfg = command(qw/config -l/); + return if grep /^svn-remote\..+\.url=/, @cfg; + my %l_map; + read_old_urls(\%l_map, '', "$ENV{GIT_DIR}/svn"); + my $migrated = 0; + + foreach my $ref_id (sort keys %l_map) { + Git::SVN->init($l_map{$ref_id}, $ref_id); + $migrated++; + } + $migrated; +} + +sub migration_check { + migrate_from_v0(); + migrate_from_v1(); + migrate_from_v2(); +} + __END__ Data structures: diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh new file mode 100755 index 0000000000..53318f1b1a --- /dev/null +++ b/t/t9107-git-svn-migrate.sh @@ -0,0 +1,63 @@ +#!/bin/sh +# Copyright (c) 2006 Eric Wong +test_description='git-svn metadata migrations from previous versions' +. ./lib-git-svn.sh + +test_expect_success 'setup old-looking metadata' " + cp $GIT_DIR/config $GIT_DIR/config-old-git-svn && + git-svn init $svnrepo && + git-svn fetch && + for i in trunk branches/a branches/b tags/0.1 tags/0.2 tags/0.3; do + mkdir -p \$i && echo hello >> \$i/README || exit 1; done && + git ls-files -o trunk branches tags | git update-index --add --stdin && + git commit -m 'test' && + git-svn dcommit && + mv $GIT_DIR/svn/* $GIT_DIR/ && + rmdir $GIT_DIR/svn && + git-update-ref refs/heads/git-svn-HEAD refs/remotes/git-svn && + git-update-ref refs/heads/svn-HEAD refs/remotes/git-svn && + git-update-ref -d refs/remotes/git-svn refs/remotes/git-svn + " + +head=`git rev-parse --verify refs/heads/git-svn-HEAD^0` +test_expect_success 'git-svn-HEAD is a real HEAD' "test -n '$head'" + +test_expect_success 'initialize old-style (v0) git-svn layout' " + mkdir -p $GIT_DIR/git-svn/info $GIT_DIR/svn/info && + echo $svnrepo > $GIT_DIR/git-svn/info/url && + echo $svnrepo > $GIT_DIR/svn/info/url && + git-svn migrate && + ! test -d $GIT_DIR/git-svn && + git-rev-parse --verify refs/remotes/git-svn^0 && + git-rev-parse --verify refs/remotes/svn^0 && + test \`git repo-config --get svn-remote.git-svn.url\` = '$svnrepo' && + test \`git repo-config --get svn-remote.git-svn.fetch\` = \ + ':refs/remotes/git-svn' + " + +test_expect_success 'initialize a multi-repository repo' " + git-svn multi-init $svnrepo -T trunk -t tags -b branches && + git-repo-config --get-all svn-remote.git-svn.fetch > fetch.out && + grep '^trunk:refs/remotes/trunk$' fetch.out && + grep '^branches/a:refs/remotes/a$' fetch.out && + grep '^branches/b:refs/remotes/b$' fetch.out && + grep '^tags/0\.1:refs/remotes/tags/0\.1$' fetch.out && + grep '^tags/0\.2:refs/remotes/tags/0\.2$' fetch.out && + grep '^tags/0\.3:refs/remotes/tags/0\.3$' fetch.out + " + +test_expect_success 'multi-fetch works on partial urls + paths' " + git-svn multi-fetch && + for i in trunk a b tags/0.1 tags/0.2 tags/0.3; do + git rev-parse --verify refs/remotes/\$i^0 >> refs.out || exit 1; + done && + test -z \"\`sort < refs.out | uniq -d\`\" && + for i in trunk a b tags/0.1 tags/0.2 tags/0.3; do + for j in trunk a b tags/0.1 tags/0.2 tags/0.3; do + if test \$j != \$i; then continue; fi + test -z \"\`git diff refs/remotes/\$i \ + refs/remotes/\$j\`\" ||exit 1; done; done + " + +test_done + From 780a2f58e727386ae81223d7a5c16fbc55cfd9fa Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 18 Jan 2007 18:15:23 -0800 Subject: [PATCH 082/201] git-svn: fix a regression in dcommit that caused empty log messages Signed-off-by: Eric Wong --- git-svn.perl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/git-svn.perl b/git-svn.perl index 9d50d305c1..b5a4cb05a6 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -339,13 +339,14 @@ sub cmd_dcommit { if ($_dry_run) { print "diff-tree $d~1 $d\n"; } else { + my $log = get_commit_entry($d)->{log}; my $ra = $gs->ra; my $pool = SVN::Pool->new; my %ed_opts = ( r => $last_rev, ra => $ra->dup, svn_path => $ra->{svn_path} ); my $ed = SVN::Git::Editor->new(\%ed_opts, - $ra->get_commit_editor($::_message, + $ra->get_commit_editor($log, sub { print "Committed r$_[0]\n"; $last_rev = $_[0]; }), $pool); From f6f0987646069bff3a7674aca257fd84f939d960 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 18 Jan 2007 18:22:18 -0800 Subject: [PATCH 083/201] git-svn: reuse open SVN::Ra connections by URL Note: this can cause problems with Perl's reference counting GC, so I'm disabling Git::SVN::Ra::DESTROY. If we notice more problems down the line, we can disable this enhancement. Signed-off-by: Eric Wong --- git-svn.perl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index b5a4cb05a6..4084e0657b 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2015,6 +2015,7 @@ package Git::SVN::Ra; use strict; use warnings; my ($can_do_switch); +my %RA; BEGIN { # enforce temporary pool usage for some simple functions @@ -2033,6 +2034,9 @@ BEGIN sub new { my ($class, $url) = @_; + $url =~ s!/+$!!; + return $RA{$url} if $RA{$url}; + SVN::_Core::svn_config_ensure($config_dir, undef); my ($baton, $callbacks) = SVN::Core::auth_open_helper([ SVN::Client::get_simple_provider(), @@ -2057,13 +2061,11 @@ sub new { $self->{svn_path} = $url; $self->{repos_root} = $self->get_repos_root; $self->{svn_path} =~ s#^\Q$self->{repos_root}\E/*##; - bless $self, $class; + $RA{$url} = bless $self, $class; } sub DESTROY { - my $self = shift; - $self->{pool}->clear if $self->{pool}; - $self->SUPER::DESTROY(@_); + # do not call the real DESTROY since we store ourselves in %RA } sub dup { From 47e39c55c91993b94824b7a317ebeb965aaeb45a Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 21 Jan 2007 04:27:09 -0800 Subject: [PATCH 084/201] git-svn: enable --minimize to simplify the config and connections --minimize will update the git-svn configuration to attempt to connect to the repository root (instead of directly to the path(s) we are tracking) in order to allow more efficient reuse of connections (for multi-fetch and follow-parent). Signed-off-by: Eric Wong --- git-svn.perl | 110 +++++++++++++++++++++++++++++++++++-- t/t9107-git-svn-migrate.sh | 25 +++++++++ 2 files changed, 129 insertions(+), 6 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 4084e0657b..15d65e21e1 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -119,8 +119,6 @@ BEGIN \%fc_opts ], 'migrate' => [ sub { }, # no-op, we automatically run this anyways, - # we may add a flag to automatically optimize the - # configuration to avoid reconnects in the future 'Migrate configuration/metadata/layout from previous versions of git-svn', \%remote_opts ], @@ -158,6 +156,8 @@ BEGIN read_repo_config(\%opts); my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version, + 'minimize-connections' => + \$Git::SVN::Migration::_minimize, 'id|i=s' => \$Git::SVN::default_repo_id); exit 1 if (!$rv && $cmd ne 'log'); @@ -702,10 +702,22 @@ BEGIN svn:entry:committed-date/; } -# we allow dashes, unlike remotes2config.sh +sub read_all_remotes { + my $r = {}; + foreach (grep { s/^svn-remote\.// } command(qw/repo-config -l/)) { + if (m!^(.+)\.fetch=\s*(.*)\s*:\s*refs/remotes/(.+)\s*$!) { + $r->{$1}->{fetch}->{$2} = $3; + } elsif (m!^(.+)\.url=\s*(.*)\s*$!) { + $r->{$1}->{url} = $2; + } + } + $r; +} + +# we allow more chars than remotes2config.sh... sub sanitize_remote_name { my ($name) = @_; - $name =~ tr/A-Za-z0-9-/./c; + $name =~ tr{A-Za-z0-9:,/+-}{.}c; $name; } @@ -2467,7 +2479,8 @@ package Git::SVN::Migration; use warnings; use Carp qw/croak/; use File::Path qw/mkpath/; -use File::Basename qw/dirname/; +use File::Basename qw/dirname basename/; +use vars qw/$_minimize/; sub migrate_from_v0 { my $git_dir = $ENV{GIT_DIR}; @@ -2577,16 +2590,101 @@ sub migrate_from_v2 { my $migrated = 0; foreach my $ref_id (sort keys %l_map) { - Git::SVN->init($l_map{$ref_id}, $ref_id); + Git::SVN->init($l_map{$ref_id}, '', $ref_id, $ref_id); $migrated++; } $migrated; } +sub minimize_connections { + my $r = Git::SVN::read_all_remotes(); + my $new_urls = {}; + my $root_repos = {}; + foreach my $repo_id (keys %$r) { + my $url = $r->{$repo_id}->{url} or next; + my $fetch = $r->{$repo_id}->{fetch} or next; + my $ra = Git::SVN::Ra->new($url); + + # skip existing cases where we already connect to the root + if (($ra->{url} eq $ra->{repos_root}) || + (Git::SVN::sanitize_remote_name($ra->{repos_root}) eq + $repo_id)) { + $root_repos->{$ra->{url}} = $repo_id; + next; + } + + my $root_ra = Git::SVN::Ra->new($ra->{repos_root}); + my $root_path = $ra->{url}; + $root_path =~ s#^\Q$ra->{repos_root}\E/*##; + foreach my $path (keys %$fetch) { + my $ref_id = $fetch->{$path}; + my $gs = Git::SVN->new($ref_id, $repo_id, $path); + + # make sure we can read when connecting to + # a higher level of a repository + my ($last_rev, undef) = $gs->last_rev_commit; + if (!defined $last_rev) { + $last_rev = eval { + $root_ra->get_latest_revnum; + }; + next if $@; + } + my $new = $root_path; + $new .= length $path ? "/$path" : ''; + eval { + $root_ra->get_log([$new], $last_rev, $last_rev, + 0, 0, 1, sub { }); + }; + next if $@; + $new_urls->{$ra->{repos_root}}->{$new} = + { ref_id => $ref_id, + old_repo_id => $repo_id, + old_path => $path }; + } + } + + my @emptied; + foreach my $url (keys %$new_urls) { + # see if we can re-use an existing [svn-remote "repo_id"] + # instead of creating a(n ugly) new section: + my $repo_id = $root_repos->{$url} || + Git::SVN::sanitize_remote_name($url); + + my $fetch = $new_urls->{$url}; + foreach my $path (keys %$fetch) { + my $x = $fetch->{$path}; + Git::SVN->init($url, $path, $repo_id, $x->{ref_id}); + my $pfx = "svn-remote.$x->{old_repo_id}"; + + my $old_fetch = quotemeta("$x->{old_path}:". + "refs/remotes/$x->{ref_id}"); + command_noisy(qw/repo-config --unset/, + "$pfx.fetch", '^'. $old_fetch . '$'); + delete $r->{$x->{old_repo_id}}-> + {fetch}->{$x->{old_path}}; + if (!keys %{$r->{$x->{old_repo_id}}->{fetch}}) { + command_noisy(qw/repo-config --unset/, + "$pfx.url"); + push @emptied, $x->{old_repo_id} + } + } + } + if (@emptied) { + my $file = $ENV{GIT_CONFIG} || $ENV{GIT_CONFIG_LOCAL} || + "$ENV{GIT_DIR}/config"; + print STDERR < $GIT_DIR/svn/\$ref/info/url ) || exit 1; + done && + git-svn migrate --minimize && + test -z \"\`git-repo-config -l |grep -v '^svn-remote\.git-svn\.'\`\" && + git-repo-config --get-all svn-remote.git-svn.fetch > fetch.out && + grep '^trunk:refs/remotes/trunk$' fetch.out && + grep '^branches/a:refs/remotes/a$' fetch.out && + grep '^branches/b:refs/remotes/b$' fetch.out && + grep '^tags/0\.1:refs/remotes/tags/0\.1$' fetch.out && + grep '^tags/0\.2:refs/remotes/tags/0\.2$' fetch.out && + grep '^tags/0\.3:refs/remotes/tags/0\.3$' fetch.out + grep '^:refs/remotes/git-svn' fetch.out + " + test_done From 15710b6f34da26d30079dbc83c797a8335040b75 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 22 Jan 2007 02:20:33 -0800 Subject: [PATCH 085/201] git-svn: fix --follow-parent to work with Git::SVN While we're at it, beef up the test because I was getting false-passes during development. Signed-off-by: Eric Wong --- git-svn.perl | 196 +++++++++++++++++-------------- t/t9104-git-svn-follow-parent.sh | 6 +- 2 files changed, 110 insertions(+), 92 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 15d65e21e1..8d49959f37 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -790,6 +790,16 @@ sub ra { $self->{ra} ||= Git::SVN::Ra->new($self->{url}); } +sub rel_path { + my ($self) = @_; + my $repos_root = $self->ra->{repos_root}; + return $self->{path} if ($self->{url} eq $repos_root); + my $url = $self->{url} . + (length $self->{path} ? "/$self->{path}" : $self->{path}); + $url =~ s!^\Q$repos_root\E/*!!g; + $url; +} + sub copy_remote_ref { my ($self) = @_; my $origin = $::_cp_remote ? $::_cp_remote : 'origin'; @@ -998,16 +1008,97 @@ sub do_git_commit { return $commit; } +sub revisions_eq { + my ($self, $r0, $r1) = @_; + return 1 if $r0 == $r1; + my $nr = 0; + $self->ra->get_log([$self->{path}], $r0, $r1, + 0, 0, 1, sub { $nr++ }); + return 0 if ($nr > 1); + return 1; +} + +sub find_parent_branch { + my ($self, $paths, $rev) = @_; + + # look for a parent from another branch: + my $i = $paths->{'/'.$self->rel_path} or return; + my $branch_from = $i->copyfrom_path or return; + my $r = $i->copyfrom_rev; + my $repos_root = $self->ra->{repos_root}; + my $url = $self->ra->{url}; + my $new_url = $repos_root . $branch_from; + print STDERR "Found possible branch point: ", + "$new_url => ", $self->full_url, ", $r\n"; + $branch_from =~ s#^/##; + my $remotes = read_all_remotes(); + my $gs; + foreach my $repo_id (keys %$remotes) { + my $u = $remotes->{$repo_id}->{url} or next; + next if $url ne $u; + my $fetch = $remotes->{$repo_id}->{fetch}; + foreach my $f (keys %$fetch) { + next if $f ne $branch_from; + $gs = Git::SVN->new($fetch->{$f}, $repo_id, $f); + last; + } + last if $gs; + } + unless ($gs) { + my $ref_id = $branch_from; + $ref_id .= "\@$r" if find_ref($ref_id); + # just grow a tail if we're not unique enough :x + $ref_id .= '-' while find_ref($ref_id); + $gs = Git::SVN->init($new_url, '', $ref_id, $ref_id); + } + my ($r0, $parent) = $gs->find_rev_before($r, 1); + if ($::_follow_parent && (!defined $r0 || !defined $parent)) { + foreach (0 .. $r) { + my $log_entry = eval { $gs->do_fetch(undef, $_) }; + $gs->do_git_commit($log_entry) if $log_entry; + } + ($r0, $parent) = $gs->last_rev_commit; + } + if (defined $r0 && defined $parent && $gs->revisions_eq($r0, $r)) { + print STDERR "Found branch parent: ($self->{ref_id}) $parent\n"; + command_noisy('read-tree', $parent); + my $ed; + if ($self->ra->can_do_switch) { + # do_switch works with svn/trunk >= r22312, but that + # is not included with SVN 1.4.2 (the latest version + # at the moment), so we can't rely on it + $self->{last_commit} = $parent; + $ed = SVN::Git::Fetcher->new($self); + $gs->ra->gs_do_switch($r0, $rev, $gs->{path}, 1, + $self->full_url, $ed) + or die "SVN connection failed somewhere...\n"; + } else { + $ed = SVN::Git::Fetcher->new($self); + $self->ra->gs_do_update($rev, $rev, $self->{path}, + 1, $ed) + or die "SVN connection failed somewhere...\n"; + } + return $self->make_log_entry($rev, [$parent], $ed); + } + print STDERR "Branch parent not found...\n"; + return undef; +} + sub do_fetch { my ($self, $paths, $rev) = @_; - my $ed = SVN::Git::Fetcher->new($self); + my $ed; my ($last_rev, @parents); if ($self->{last_commit}) { + $ed = SVN::Git::Fetcher->new($self); $last_rev = $self->{last_rev}; $ed->{c} = $self->{last_commit}; @parents = ($self->{last_commit}); } else { $last_rev = $rev; + if (my $log_entry = $self->find_parent_branch($paths, $rev)) { + return $log_entry; + } + $ed = SVN::Git::Fetcher->new($self); } unless ($self->ra->gs_do_update($last_rev, $rev, $self->{path}, 1, $ed)) { @@ -1120,9 +1211,9 @@ sub fetch { my @revs; $self->ra->get_log([''], $min, $max, 0, 1, 1, sub { my ($paths, $rev, $author, $date, $log) = @_; - push @revs, $rev }); + push @revs, [ $paths, $rev ] }); foreach (@revs) { - my $log_entry = $self->do_fetch(undef, $_); + my $log_entry = $self->do_fetch(@$_); $self->do_git_commit($log_entry, @parents); } last if $max >= $head; @@ -1232,6 +1323,18 @@ sub rev_db_get { $ret; } +sub find_rev_before { + my ($self, $rev, $eq_ok) = @_; + --$rev unless $eq_ok; + while ($rev > 0) { + if (my $c = $self->rev_db_get($rev)) { + return ($rev, $c); + } + --$rev; + } + return (undef, undef); +} + sub _new { my ($class, $repo_id, $ref_id, $path) = @_; unless (defined $repo_id && length $repo_id) { @@ -1398,93 +1501,6 @@ sub uri_decode { $f } -sub revisions_eq { - my ($path, $r0, $r1) = @_; - return 1 if $r0 == $r1; - my $nr = 0; - # should be OK to use Pool here (r1 - r0) should be small - $SVN->get_log([$path], $r0, $r1, 0, 0, 1, sub {$nr++}); - return 0 if ($nr > 1); - return 1; -} - -sub libsvn_find_parent_branch { - my ($paths, $rev, $author, $date, $log) = @_; - my $svn_path = '/'.$SVN->{svn_path}; - - # look for a parent from another branch: - my $i = $paths->{$svn_path} or return; - my $branch_from = $i->copyfrom_path or return; - my $r = $i->copyfrom_rev; - print STDERR "Found possible branch point: ", - "$branch_from => $svn_path, $r\n"; - $branch_from =~ s#^/##; - my $l_map = {}; - read_url_paths_all($l_map, '', "$GIT_DIR/svn"); - my $url = $SVN->{repos_root}; - defined $l_map->{$url} or return; - my $id = $l_map->{$url}->{$branch_from}; - if (!defined $id && $_follow_parent) { - print STDERR "Following parent: $branch_from\@$r\n"; - # auto create a new branch and follow it - $id = basename($branch_from); - $id .= '@'.$r if -r "$GIT_DIR/svn/$id"; - while (-r "$GIT_DIR/svn/$id") { - # just grow a tail if we're not unique enough :x - $id .= '-'; - } - } - return unless defined $id; - - my ($r0, $parent) = find_rev_before($r,$id,1); - if ($_follow_parent && (!defined $r0 || !defined $parent)) { - defined(my $pid = fork) or croak $!; - if (!$pid) { - $GIT_SVN = $ENV{GIT_SVN_ID} = $id; - init_vars(); - $SVN_URL = "$url/$branch_from"; - $SVN = undef; - setup_git_svn(); - # we can't assume SVN_URL exists at r+1: - $_revision = "0:$r"; - fetch_lib(); - exit 0; - } - waitpid $pid, 0; - croak $? if $?; - ($r0, $parent) = find_rev_before($r,$id,1); - } - return unless (defined $r0 && defined $parent); - if (revisions_eq($branch_from, $r0, $r)) { - unlink $GIT_SVN_INDEX; - print STDERR "Found branch parent: ($GIT_SVN) $parent\n"; - command_noisy('read-tree', $parent); - unless ($SVN->can_do_switch) { - return _libsvn_new_tree($paths, $rev, $author, $date, - $log, [$parent]); - } - # do_switch works with svn/trunk >= r22312, but that is not - # included with SVN 1.4.2 (the latest version at the moment), - # so we can't rely on it. - my $ra = Git::SVN::Ra->new("$url/$branch_from"); - my $ed = SVN::Git::Fetcher->new({c => $parent, q => $_q }); - $ra->gs_do_switch($r0, $rev, '', 1, $SVN->{url}, $ed) or - die "SVN connection failed somewhere...\n"; - return libsvn_log_entry($rev, $author, $date, $log, [$parent]); - } - print STDERR "Nope, branch point not imported or unknown\n"; - return undef; -} - -sub _libsvn_new_tree { - my ($paths, $rev, $author, $date, $log, $parents) = @_; - my $ed = SVN::Git::Fetcher->new({q => $_q}); - unless ($SVN->gs_do_update($rev, $rev, '', 1, $ed)) { - die "SVN connection failed somewhere...\n"; - } - libsvn_log_entry($rev, $author, $date, $log, $parents, $ed); -} - { my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file. $SVN::Node::dir.$SVN::Node::unknown. diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 405b555368..91fdfe964e 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -30,8 +30,10 @@ test_expect_success 'initialize repo' " test_expect_success 'init and fetch --follow-parent a moved directory' " git-svn init -i thunk $svnrepo/thunk && git-svn fetch --follow-parent -i thunk && - git-rev-parse --verify refs/remotes/trunk && - test '$?' -eq '0' + test \"\`git-rev-parse --verify refs/remotes/trunk\`\" \ + = \"\`git-rev-parse --verify refs/remotes/thunk~1\`\" && + test \"\`git-cat-file blob refs/remotes/thunk:readme |\ + sed -n -e '3p'\`\" = goodbye " test_debug 'gitk --all &' From 8b8fc06824cde2b314807e5e3a20e0adfd948cda Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 22 Jan 2007 11:44:57 -0800 Subject: [PATCH 086/201] git-svn: --follow-parent works with svn-remotes multiple branches Bugs fixed: * We didn't allow manually (not using git-svn) init-ed remotes/fetch refspecs to be used before. It works now because that's what I did in this test. git-svn init should offer more control in the future. * correctly strip paths in the delta editor when using do_switch(). * Make the -i / GIT_SVN_ID option work correctly when doing fetch on a multi-ref svn-remote Signed-off-by: Eric Wong --- git-svn.perl | 49 +++++++++++++++++++++----------- t/t9104-git-svn-follow-parent.sh | 13 +++++++++ 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 8d49959f37..84f4679570 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -14,7 +14,8 @@ $VERSION = '@@GIT_VERSION@@'; $ENV{GIT_DIR} ||= '.git'; -$Git::SVN::default_repo_id = $ENV{GIT_SVN_ID} || 'git-svn'; +$Git::SVN::default_repo_id = 'git-svn'; +$Git::SVN::default_ref_id = $ENV{GIT_SVN_ID} || 'git-svn'; my $LC_ALL = $ENV{LC_ALL}; $Git::SVN::Log::TZ = $ENV{TZ}; @@ -158,7 +159,7 @@ BEGIN 'version|V' => \$_version, 'minimize-connections' => \$Git::SVN::Migration::_minimize, - 'id|i=s' => \$Git::SVN::default_repo_id); + 'id|i=s' => \$Git::SVN::default_ref_id); exit 1 if (!$rv && $cmd ne 'log'); usage(0) if $_help; @@ -686,7 +687,7 @@ sub tz_to_s_offset { package Git::SVN; use strict; use warnings; -use vars qw/$default_repo_id/; +use vars qw/$default_repo_id $default_ref_id/; use Carp qw/croak/; use File::Path qw/mkpath/; use IPC::Open3; @@ -704,7 +705,7 @@ BEGIN sub read_all_remotes { my $r = {}; - foreach (grep { s/^svn-remote\.// } command(qw/repo-config -l/)) { + foreach (grep { s/^svn-remote\.// } command(qw/config -l/)) { if (m!^(.+)\.fetch=\s*(.*)\s*:\s*refs/remotes/(.+)\s*$!) { $r->{$1}->{fetch}->{$2} = $3; } elsif (m!^(.+)\.url=\s*(.*)\s*$!) { @@ -724,7 +725,6 @@ sub sanitize_remote_name { sub init { my ($class, $url, $path, $repo_id, $ref_id) = @_; my $self = _new($class, $repo_id, $ref_id, $path); - mkpath([$self->{dir}]); if (defined $url) { $url =~ s!/+$!!; # strip trailing slash my $orig_url = eval { @@ -745,10 +745,6 @@ sub init { "$path:".$self->refname); } $self->{url} = $url; - unless (-f $self->{db_path}) { - open my $fh, '>>', $self->{db_path} or croak $!; - close $fh or croak $!; - } $self; } @@ -777,6 +773,14 @@ sub new { } } my $self = _new($class, $repo_id, $ref_id, $path); + if (!defined $self->{path} || !length $self->{path}) { + my $fetch = command_oneline('config', '--get', + "svn-remote.$repo_id.fetch", + ":refs/remotes/$ref_id\$") or + die "Failed to read \"svn-remote.$repo_id.fetch\" ", + "\":refs/remotes/$ref_id\$\" in config\n"; + ($self->{path}, undef) = split(/\s*:\s*/, $fetch); + } $self->{url} = command_oneline('config', '--get', "svn-remote.$repo_id.url") or die "Failed to read \"svn-remote.$repo_id.url\" in config\n"; @@ -1064,6 +1068,7 @@ sub find_parent_branch { command_noisy('read-tree', $parent); my $ed; if ($self->ra->can_do_switch) { + print STDERR "Following parent with do_switch\n"; # do_switch works with svn/trunk >= r22312, but that # is not included with SVN 1.4.2 (the latest version # at the moment), so we can't rely on it @@ -1073,6 +1078,7 @@ sub find_parent_branch { $self->full_url, $ed) or die "SVN connection failed somewhere...\n"; } else { + print STDERR "Following parent with do_update\n"; $ed = SVN::Git::Fetcher->new($self); $self->ra->gs_do_update($rev, $rev, $self->{path}, 1, $ed) @@ -1209,7 +1215,7 @@ sub fetch { $SVN::Error::handler = \&skip_unknown_revs; while (1) { my @revs; - $self->ra->get_log([''], $min, $max, 0, 1, 1, sub { + $self->ra->get_log([$self->{path}], $min, $max, 0, 1, 1, sub { my ($paths, $rev, $author, $date, $log) = @_; push @revs, [ $paths, $rev ] }); foreach (@revs) { @@ -1341,11 +1347,16 @@ sub _new { $repo_id = $Git::SVN::default_repo_id; } unless (defined $ref_id && length $ref_id) { - $_[2] = $ref_id = $repo_id; + $_[2] = $ref_id = $Git::SVN::default_ref_id; } $_[1] = $repo_id = sanitize_remote_name($repo_id); my $dir = "$ENV{GIT_DIR}/svn/$ref_id"; $_[3] = $path = '' unless (defined $path); + mkpath([$dir]); + unless (-f "$dir/.rev_db") { + open my $fh, '>>', "$dir/.rev_db" or croak $!; + close $fh or croak $!; + } bless { ref_id => $ref_id, dir => $dir, index => "$dir/index", path => $path, db_path => "$dir/.rev_db", repo_id => $repo_id }, $class; @@ -1526,9 +1537,6 @@ sub new { my $self = SVN::Delta::Editor->new; bless $self, $class; $self->{c} = $git_svn->{last_commit} if exists $git_svn->{last_commit}; - if (length $git_svn->{path}) { - $self->{path_strip} = qr/\Q$git_svn->{path}\E\/?/; - } $self->{empty} = {}; $self->{dir_prop} = {}; $self->{file_prop} = {}; @@ -1540,6 +1548,11 @@ sub new { $self; } +sub set_path_strip { + my ($self, $path) = @_; + $self->{path_strip} = qr/^\Q$path\E\/?/; +} + sub open_root { { path => '' }; } @@ -2128,6 +2141,7 @@ sub uuid { sub gs_do_update { my ($self, $rev_a, $rev_b, $path, $recurse, $editor) = @_; my $pool = SVN::Pool->new; + $editor->set_path_strip($path); my $reporter = $self->do_update($rev_b, $path, $recurse, $editor, $pool); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); @@ -2141,10 +2155,11 @@ sub gs_do_update { sub gs_do_switch { my ($self, $rev_a, $rev_b, $path, $recurse, $url_b, $editor) = @_; my $pool = SVN::Pool->new; + $editor->set_path_strip($path); my $reporter = $self->do_switch($rev_b, $path, $recurse, $url_b, $editor, $pool); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); - $reporter->set_path($path, $rev_a, 0, @lock, $pool); + $reporter->set_path('', $rev_a, 0, @lock, $pool); $reporter->finish_report($pool); $pool->clear; $editor->{git_commit_ok}; @@ -2674,12 +2689,12 @@ sub minimize_connections { my $old_fetch = quotemeta("$x->{old_path}:". "refs/remotes/$x->{ref_id}"); - command_noisy(qw/repo-config --unset/, + command_noisy(qw/config --unset/, "$pfx.fetch", '^'. $old_fetch . '$'); delete $r->{$x->{old_repo_id}}-> {fetch}->{$x->{old_path}}; if (!keys %{$r->{$x->{old_repo_id}}->{fetch}}) { - command_noisy(qw/repo-config --unset/, + command_noisy(qw/config --unset/, "$pfx.url"); push @emptied, $x->{old_repo_id} } diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 91fdfe964e..3afec978d6 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -36,6 +36,19 @@ test_expect_success 'init and fetch --follow-parent a moved directory' " sed -n -e '3p'\`\" = goodbye " +test_expect_success 'init and fetch from one svn-remote' " + git-repo-config svn-remote.git-svn.url $svnrepo && + git-repo-config --add svn-remote.git-svn.fetch \ + trunk:refs/remotes/svn/trunk && + git-repo-config --add svn-remote.git-svn.fetch \ + thunk:refs/remotes/svn/thunk && + git-svn fetch --follow-parent -i svn/thunk && + test \"\`git-rev-parse --verify refs/remotes/svn/trunk\`\" \ + = \"\`git-rev-parse --verify refs/remotes/svn/thunk~1\`\" && + test \"\`git-cat-file blob refs/remotes/svn/thunk:readme |\ + sed -n -e '3p'\`\" = goodbye + " + test_debug 'gitk --all &' test_done From b805b44a923e32251af1abd4e8d7bf5f7d4d8ef6 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 22 Jan 2007 13:52:04 -0800 Subject: [PATCH 087/201] git-svn: disallow ambigious local refspecs Having multiple fetch refspecs pointing to the same local ref would be a very bad thing. Start avoiding the use of fatal() or exit() inside the modules so we can libify more easily. Signed-off-by: Eric Wong --- git-svn.perl | 40 +++++++++++++++++++++++++++++++++------- t/t9100-git-svn-basic.sh | 11 +++++++++++ 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 84f4679570..f01fb9a35d 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -169,7 +169,11 @@ BEGIN unless ($cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/) { Git::SVN::Migration::migration_check(); } -$cmd{$cmd}->[0]->(@ARGV); +eval { + Git::SVN::verify_remotes_sanity(); + $cmd{$cmd}->[0]->(@ARGV); +}; +fatal $@ if $@; exit 0; ####################### primary functions ###################### @@ -715,6 +719,22 @@ sub read_all_remotes { $r; } +sub verify_remotes_sanity { + my %seen; + foreach (command(qw/config -l/)) { + if (m!^svn-remote\.(?:.+)\.fetch=.*:refs/remotes/(\S+)\s*$!) { + if ($seen{$1}) { + die "Remote ref refs/remote/$1 is tracked by", + "\n \"$_\"\nand\n \"$seen{$1}\"\n", + "Please resolve this ambiguity in ", + "your git configuration file before ", + "continuing\n"; + } + $seen{$1} = $_; + } + } +} + # we allow more chars than remotes2config.sh... sub sanitize_remote_name { my ($name) = @_; @@ -727,16 +747,22 @@ sub init { my $self = _new($class, $repo_id, $ref_id, $path); if (defined $url) { $url =~ s!/+$!!; # strip trailing slash + + # verify that we aren't overwriting anything: my $orig_url = eval { command_oneline('config', '--get', "svn-remote.$repo_id.url") }; - if ($orig_url) { - if ($orig_url ne $url) { - die "svn-remote.$repo_id.url already set: ", - "$orig_url\nwanted to set to: $url\n"; - } - } else { + if ($orig_url && ($orig_url ne $url)) { + die "svn-remote.$repo_id.url already set: ", + "$orig_url\nwanted to set to: $url\n"; + } + my ($xrepo_id, $xpath) = find_ref($self->refname); + if (defined $xpath) { + die "svn-remote.$xrepo_id.fetch already set to track ", + "$xpath:refs/remotes/", $self->refname, "\n"; + } + if (!$orig_url) { command_noisy('config', "svn-remote.$repo_id.url", $url); } diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index 040da92756..af617486dd 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -215,4 +215,15 @@ echo tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 >> expected test_expect_success "$name" "diff -u a expected" +test_expect_failure 'exit if remote refs are ambigious' " + git-repo-config --add svn-remote.git-svn.fetch \ + bar:refs/remotes/git-svn && + git-svn migrate + " +test_expect_failure 'exit if init-ing a would clobber a URL' " + git-repo-config --unset svn-remote.git-svn.fetch \ + '^bar:refs/remotes/git-svn$' && + git-svn init $svnrepo/bar + " + test_done From a2003abc23a5961534e8a0cc70b881eb78d54328 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 22 Jan 2007 15:22:50 -0800 Subject: [PATCH 088/201] git-svn: allow --follow-parent on deleted directories Any operations on the index in Git::SVN that is not wrapped by tmp_index_do() is wrong. Signed-off-by: Eric Wong --- git-svn.perl | 2 +- t/t9104-git-svn-follow-parent.sh | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/git-svn.perl b/git-svn.perl index f01fb9a35d..88c022701d 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1091,7 +1091,7 @@ sub find_parent_branch { } if (defined $r0 && defined $parent && $gs->revisions_eq($r0, $r)) { print STDERR "Found branch parent: ($self->{ref_id}) $parent\n"; - command_noisy('read-tree', $parent); + $self->assert_index_clean($parent); my $ed; if ($self->ra->can_do_switch) { print STDERR "Following parent with do_switch\n"; diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 3afec978d6..402b614c76 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -49,6 +49,18 @@ test_expect_success 'init and fetch from one svn-remote' " sed -n -e '3p'\`\" = goodbye " +test_expect_success 'follow deleted parent' " + svn cp -m 'resurrecting trunk as junk' \ + -r2 $svnrepo/trunk $svnrepo/junk && + git-repo-config --add svn-remote.git-svn.fetch \ + junk:refs/remotes/svn/junk && + git-svn fetch --follow-parent -i svn/thunk && + git-svn fetch -i svn/junk --follow-parent && + test -z \"\`git diff svn/junk svn/trunk\`\" && + test \"\`git merge-base svn/junk svn/trunk\`\" \ + = \"\`git rev-parse svn/trunk\`\" + " + test_debug 'gitk --all &' test_done From 07a1c95045a8c4983d3868f6070d9fa9ba5ff596 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 22 Jan 2007 15:47:41 -0800 Subject: [PATCH 089/201] git-svn: get rid of additional fetch-arguments It's not really useful anymore now that we have a better --follow-parent for the valid cases. Any other use of it is not valid. Signed-off-by: Eric Wong --- Documentation/git-svn.txt | 24 ------------------------ git-svn.perl | 7 ++++++- 2 files changed, 6 insertions(+), 25 deletions(-) diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 6ce6a3944d..6daba241e9 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -49,9 +49,6 @@ remotes/git-svn and work on that branch. Use the 'dcommit' command (see below) to write git commits back to remotes/git-svn. -See '<>' if you are interested in -manually joining branches on commit. - 'dcommit':: Commit each diff from a specified head directly to the SVN repository, and then rebase or reset (depending on whether or @@ -443,27 +440,6 @@ be remotes/$GIT_SVN_ID, instead of remotes/git-svn. Any remotes/$GIT_SVN_ID branch should never be modified by the user outside of git-svn commands. -[[fetch-args]] -ADDITIONAL FETCH ARGUMENTS --------------------------- -This is for advanced users, most users should ignore this section. - -Unfetched SVN revisions may be imported as children of existing commits -by specifying additional arguments to 'fetch'. Additional parents may -optionally be specified in the form of sha1 hex sums at the -command-line. Unfetched SVN revisions may also be tied to particular -git commits with the following syntax: - ------------------------------------------------- - svn_revision_number=git_commit_sha1 ------------------------------------------------- - -This allows you to tie unfetched SVN revision 375 to your current HEAD: - ------------------------------------------------- - git-svn fetch 375=$(git-rev-parse HEAD) ------------------------------------------------- - If you're tracking a directory that has moved, or otherwise been branched or tagged off of another directory in the repository and you care about the full history of the project, then you can use diff --git a/git-svn.perl b/git-svn.perl index 88c022701d..2e3d35527e 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -277,8 +277,13 @@ sub cmd_init { } sub cmd_fetch { + if (@_) { + die "Additional fetch arguments are no longer supported.\n", + "Use --follow-parent if you have moved/copied directories + instead.\n"; + } my $gs = Git::SVN->new; - $gs->fetch(@_); + $gs->fetch; if ($gs->{last_commit} && !verify_ref('refs/heads/master^0')) { command_noisy(qw(update-ref refs/heads/master), $gs->{last_commit}); From 536c4b09370f3f443fbe87284d2378fd21f94350 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 23 Jan 2007 11:35:53 -0800 Subject: [PATCH 090/201] git-svn: allow 'init' to work outside of tests Tests always ran 'git init' before we ran so that repo-config would always have something to read. However that does not work in real-world situations where the user expects 'git svn init' to work without running 'git init' first. Signed-off-by: Eric Wong --- git-svn.perl | 1 + 1 file changed, 1 insertion(+) diff --git a/git-svn.perl b/git-svn.perl index 2e3d35527e..a70e7b9110 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -725,6 +725,7 @@ sub read_all_remotes { } sub verify_remotes_sanity { + return unless -d $ENV{GIT_DIR}; my %seen; foreach (command(qw/config -l/)) { if (m!^svn-remote\.(?:.+)\.fetch=.*:refs/remotes/(\S+)\s*$!) { From 9bf046372b370fba8958ba6ef9dc63b232d7637c Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 23 Jan 2007 13:03:29 -0800 Subject: [PATCH 091/201] git-svn: better error reporting if --follow-parent fails This will be useful to me when I try more special-cases of parent-tracking. Signed-off-by: Eric Wong --- git-svn.perl | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index a70e7b9110..de026b4e4c 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1056,10 +1056,12 @@ sub revisions_eq { sub find_parent_branch { my ($self, $paths, $rev) = @_; + return undef unless $::_follow_parent; # look for a parent from another branch: - my $i = $paths->{'/'.$self->rel_path} or return; - my $branch_from = $i->copyfrom_path or return; + my $abs_path = '/'.$self->rel_path; + my $i = $paths->{$abs_path} or goto not_found; + my $branch_from = $i->copyfrom_path or goto not_found; my $r = $i->copyfrom_rev; my $repos_root = $self->ra->{repos_root}; my $url = $self->ra->{url}; @@ -1118,7 +1120,16 @@ sub find_parent_branch { } return $self->make_log_entry($rev, [$parent], $ed); } - print STDERR "Branch parent not found...\n"; +not_found: + print STDERR "Branch parent for path: '$abs_path' not found\n"; + return undef unless $paths; + foreach my $p (sort keys %$paths) { + print STDERR ' ', $p->action, ' ', $p; + if (my $cp_from = $p->copyfrom_path) { + print STDERR "(from $cp_from:", $p->copyfrom_rev, ')'; + } + print STDERR "\n"; + } return undef; } From e6434f876097f196acbd9a806637d0f6076752fd Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 23 Jan 2007 16:29:23 -0800 Subject: [PATCH 092/201] git-svn: 'init' attempts to connect to the repository root if possible This allows connections to be used more efficiently and not require users to run 'git-svn migrate --minimize' for new repositories. Signed-off-by: Eric Wong --- git-svn.perl | 104 ++++++++++++++++++++++++++++++--------- t/t9100-git-svn-basic.sh | 14 +++++- 2 files changed, 93 insertions(+), 25 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index de026b4e4c..d290a0d8ee 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -748,35 +748,78 @@ sub sanitize_remote_name { $name; } +sub find_existing_remote { + my ($url, $remotes) = @_; + my $existing; + foreach my $repo_id (keys %$remotes) { + my $u = $remotes->{$repo_id}->{url} or next; + next if $u ne $url; + $existing = $repo_id; + last; + } + $existing; +} + +sub init_remote_config { + my ($self, $url) = @_; + $url =~ s!/+$!!; # strip trailing slash + my $r = read_all_remotes(); + my $existing = find_existing_remote($url, $r); + if ($existing) { + print STDERR "Using existing ", + "[svn-remote \"$existing\"]\n"; + $self->{repo_id} = $existing; + } else { + my $min_url = Git::SVN::Ra->new($url)->minimize_url; + $existing = find_existing_remote($min_url, $r); + if ($existing) { + print STDERR "Using existing ", + "[svn-remote \"$existing\"]\n"; + $self->{repo_id} = $existing; + } + if ($min_url ne $url) { + print STDERR "Using higher level of URL: ", + "$url => $min_url\n"; + my $old_path = $self->{path}; + $self->{path} = $url; + $self->{path} =~ s!^\Q$min_url\E/*!!; + if (length $old_path) { + $self->{path} .= "/$old_path"; + } + $url = $min_url; + } + } + my $orig_url; + if (!$existing) { + # verify that we aren't overwriting anything: + $orig_url = eval { + command_oneline('config', '--get', + "svn-remote.$self->{repo_id}.url") + }; + if ($orig_url && ($orig_url ne $url)) { + die "svn-remote.$self->{repo_id}.url already set: ", + "$orig_url\nwanted to set to: $url\n"; + } + } + my ($xrepo_id, $xpath) = find_ref($self->refname); + if (defined $xpath) { + die "svn-remote.$xrepo_id.fetch already set to track ", + "$xpath:refs/remotes/", $self->refname, "\n"; + } + command_noisy('config', + "svn-remote.$self->{repo_id}.url", $url); + command_noisy('config', '--add', + "svn-remote.$self->{repo_id}.fetch", + "$self->{path}:".$self->refname); + $self->{url} = $url; +} + sub init { my ($class, $url, $path, $repo_id, $ref_id) = @_; my $self = _new($class, $repo_id, $ref_id, $path); if (defined $url) { - $url =~ s!/+$!!; # strip trailing slash - - # verify that we aren't overwriting anything: - my $orig_url = eval { - command_oneline('config', '--get', - "svn-remote.$repo_id.url") - }; - if ($orig_url && ($orig_url ne $url)) { - die "svn-remote.$repo_id.url already set: ", - "$orig_url\nwanted to set to: $url\n"; - } - my ($xrepo_id, $xpath) = find_ref($self->refname); - if (defined $xpath) { - die "svn-remote.$xrepo_id.fetch already set to track ", - "$xpath:refs/remotes/", $self->refname, "\n"; - } - if (!$orig_url) { - command_noisy('config', - "svn-remote.$repo_id.url", $url); - } - command_noisy('config', '--add', - "svn-remote.$repo_id.fetch", - "$path:".$self->refname); + $self->init_remote_config($url); } - $self->{url} = $url; $self; } @@ -2208,6 +2251,19 @@ sub gs_do_switch { $editor->{git_commit_ok}; } +sub minimize_url { + my ($self) = @_; + return $self->{url} if ($self->{url} eq $self->{repos_root}); + my $url = $self->{repos_root}; + my @components = split(m!/!, $self->{svn_path}); + my $c = ''; + do { + $url .= "/$c" if length $c; + eval { (ref $self)->new($url)->get_latest_revnum }; + } while ($@ && ($c = shift @components)); + $url; +} + sub can_do_switch { my $self = shift; unless (defined $can_do_switch) { diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index af617486dd..97798c4d07 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -220,10 +220,22 @@ test_expect_failure 'exit if remote refs are ambigious' " bar:refs/remotes/git-svn && git-svn migrate " + test_expect_failure 'exit if init-ing a would clobber a URL' " + svnadmin create ${PWD}/svnrepo2 && + svn mkdir -m 'mkdir bar' ${svnrepo}2/bar && git-repo-config --unset svn-remote.git-svn.fetch \ '^bar:refs/remotes/git-svn$' && - git-svn init $svnrepo/bar + git-svn init ${svnrepo}2/bar + " + +test_expect_success \ + 'init allows us to connect to another directory in the same repo' " + git-svn init -i bar $svnrepo/bar && + git repo-config --get svn-remote.git-svn.fetch \ + '^bar:refs/remotes/bar$' && + git repo-config --get svn-remote.git-svn.fetch \ + '^:refs/remotes/git-svn$' " test_done From 7f578c55af80e9346135004bd47099cbb451f859 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 24 Jan 2007 02:16:25 -0800 Subject: [PATCH 093/201] git-svn: --follow-parent now works on sub-directories of larger branches This means that tracking the path of: /another-larger/trunk/thunk/bump/thud inside a repository would follow: /larger-parent/trunk/thunk/bump/thud even if the svn log output looks like this: -------------------------------------------- Changed paths: A /another-larger (from /larger-parent:5) -------------------------------------------- Note: the usage of get_log() in git-svn still makes a an assumption that shouldn't be made with regard to revisions existing for a particular path. Signed-off-by: Eric Wong --- git-svn.perl | 31 +++++++++++++++++++++++-------- t/t9104-git-svn-follow-parent.sh | 17 +++++++++++++++++ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index d290a0d8ee..123d4d63f4 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1102,9 +1102,21 @@ sub find_parent_branch { return undef unless $::_follow_parent; # look for a parent from another branch: - my $abs_path = '/'.$self->rel_path; - my $i = $paths->{$abs_path} or goto not_found; + my @b_path_components = split m#/#, $self->rel_path; + my @a_path_components; + my $i; + while (@b_path_components) { + $i = $paths->{'/'.join('/', @b_path_components)}; + last if $i; + unshift(@a_path_components, pop(@b_path_components)); + } + goto not_found unless defined $i; my $branch_from = $i->copyfrom_path or goto not_found; + if (@a_path_components) { + print STDERR "branch_from: $branch_from => "; + $branch_from .= '/'.join('/', @a_path_components); + print STDERR $branch_from, "\n"; + } my $r = $i->copyfrom_rev; my $repos_root = $self->ra->{repos_root}; my $url = $self->ra->{url}; @@ -1134,10 +1146,11 @@ sub find_parent_branch { } my ($r0, $parent) = $gs->find_rev_before($r, 1); if ($::_follow_parent && (!defined $r0 || !defined $parent)) { - foreach (0 .. $r) { - my $log_entry = eval { $gs->do_fetch(undef, $_) }; + $gs->ra->get_log([$gs->{path}], 0, $r, 0, 1, 1, sub { + my ($paths, $rev) = @_; + my $log_entry = eval { $gs->do_fetch($paths, $rev) }; $gs->do_git_commit($log_entry) if $log_entry; - } + }); ($r0, $parent) = $gs->last_rev_commit; } if (defined $r0 && defined $parent && $gs->revisions_eq($r0, $r)) { @@ -1164,10 +1177,12 @@ sub find_parent_branch { return $self->make_log_entry($rev, [$parent], $ed); } not_found: - print STDERR "Branch parent for path: '$abs_path' not found\n"; + print STDERR "Branch parent for path: '/", + $self->rel_path, "' not found\n"; return undef unless $paths; - foreach my $p (sort keys %$paths) { - print STDERR ' ', $p->action, ' ', $p; + foreach my $x (sort keys %$paths) { + my $p = $paths->{$x}; + print STDERR ' ', $p->action, ' ', $x; if (my $cp_from = $p->copyfrom_path) { print STDERR "(from $cp_from:", $p->copyfrom_rev, ')'; } diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 402b614c76..22b45a6602 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -61,6 +61,23 @@ test_expect_success 'follow deleted parent' " = \"\`git rev-parse svn/trunk\`\" " +test_expect_success 'follow larger parent' " + mkdir -p import/trunk/thunk/bump/thud && + echo hi > import/trunk/thunk/bump/thud/file && + svn import -m 'import a larger parent' import $svnrepo/larger-parent && + svn cp -m 'hi' $svnrepo/larger-parent $svnrepo/another-larger && + git-svn init -i larger $svnrepo/another-larger/trunk/thunk/bump/thud && + git-svn fetch -i larger --follow-parent && + git-rev-parse --verify refs/remotes/larger && + git-rev-parse --verify \ + refs/remotes/larger-parent/trunk/thunk/bump/thud && + test \"\`git-merge-base \ + refs/remotes/larger-parent/trunk/thunk/bump/thud \ + refs/remotes/larger\`\" = \ + \"\`git-rev-parse refs/remotes/larger\`\" + true + " + test_debug 'gitk --all &' test_done From ef3cfaad19f2587ea4ff7d46574d9118f8d9555e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 24 Jan 2007 03:30:57 -0800 Subject: [PATCH 094/201] git-svn: track writes writes to the index in fetch Introducing Git::IndexInfo. This module will probably be useful outside of git-svn, so I'm not putting it in the Git::SVN namespace. This will allow me to more easily avoid the use of get_log() in the future and simply run do_update in incrementing ranges. get_log() should be avoided because there are cases where moved/deleted directories do not track correctly (until --follow-parent is run on a new branch). Signed-off-by: Eric Wong --- git-svn.perl | 50 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 123d4d63f4..a19afb83fb 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1643,8 +1643,7 @@ sub new { $self->{file_prop} = {}; $self->{absent_dir} = {}; $self->{absent_file} = {}; - ($self->{gui}, $self->{ctx}) = $git_svn->tmp_index_do( - sub { command_input_pipe(qw/update-index -z --index-info/) } ); + $self->{gii} = $git_svn->tmp_index_do(sub { Git::IndexInfo->new }); require Digest::MD5; $self; } @@ -1671,7 +1670,6 @@ sub git_path { sub delete_entry { my ($self, $path, $rev, $pb) = @_; - my $gui = $self->{gui}; my $gpath = $self->git_path($path); # remove entire directories. @@ -1681,14 +1679,15 @@ sub delete_entry { $self->{c}, '--', $gpath); local $/ = "\0"; while (<$ls>) { - print $gui '0 ',0 x 40,"\t",$_ or croak $!; + chomp; + $self->{gii}->remove($_); print "\tD\t$_\n" unless $self->{q}; } print "\tD\t$gpath/\n" unless $self->{q}; command_close_pipe($ls, $ctx); $self->{empty}->{$path} = 0 } else { - print $gui '0 ',0 x 40,"\t",$gpath,"\0" or croak $!; + $self->{gii}->remove($gpath); print "\tD\t$gpath\n" unless $self->{q}; } undef; @@ -1824,22 +1823,23 @@ sub close_file { $hash = $fb->{blob} or die "no blob information\n"; } $fb->{pool}->clear; - my $gui = $self->{gui}; - print $gui "$fb->{mode_b} $hash\t$path\0" or croak $!; + $self->{gii}->update($fb->{mode_b}, $hash, $path) or croak $!; print "\t$fb->{action}\t$path\n" if $fb->{action} && ! $self->{q}; undef; } sub abort_edit { my $self = shift; - eval { command_close_pipe($self->{gui}, $self->{ctx}) }; + $self->{nr} = $self->{gii}->{nr}; + delete $self->{gii}; $self->SUPER::abort_edit(@_); } sub close_edit { my $self = shift; - command_close_pipe($self->{gui}, $self->{ctx}); $self->{git_commit_ok} = 1; + $self->{nr} = $self->{gii}->{nr}; + delete $self->{gii}; $self->SUPER::close_edit(@_); } @@ -2832,6 +2832,38 @@ sub migration_check { minimize_connections() if $_minimize; } +package Git::IndexInfo; +use strict; +use warnings; +use Git qw/command_input_pipe command_close_pipe/; + +sub new { + my ($class) = @_; + my ($gui, $ctx) = command_input_pipe(qw/update-index -z --index-info/); + bless { gui => $gui, ctx => $ctx, nr => 0}, $class; +} + +sub remove { + my ($self, $path) = @_; + if (print { $self->{gui} } '0 ', 0 x 40, "\t", $path, "\0") { + return ++$self->{nr}; + } + undef; +} + +sub update { + my ($self, $mode, $hash, $path) = @_; + if (print { $self->{gui} } $mode, ' ', $hash, "\t", $path, "\0") { + return ++$self->{nr}; + } + undef; +} + +sub DESTROY { + my ($self) = @_; + command_close_pipe($self->{gui}, $self->{ctx}); +} + __END__ Data structures: From 1492b4245ad735e74f226bd796dc85de4f843739 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 25 Jan 2007 10:52:36 -0800 Subject: [PATCH 095/201] git-svn: add an odd test case that seems to cause segfaults over HTTP Signed-off-by: Eric Wong --- t/t9104-git-svn-follow-parent.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 22b45a6602..615c863b94 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -78,6 +78,21 @@ test_expect_success 'follow larger parent' " true " +# This seems to cause segfaults over HTTP... +test_expect_success 'follow higher-level parent' " + svn mkdir -m 'follow higher-level parent' $svnrepo/blob && + svn co $svnrepo/blob blob && + cd blob && + echo hi > hi && + svn add hi && + svn commit -m 'hi' && + cd .. + svn mkdir -m 'new glob at top level' $svnrepo/glob && + svn mv -m 'move blob down a level' $svnrepo/blob $svnrepo/glob/blob && + git-svn init -i blob $svnrepo/glob/blob && + git-svn fetch -i blob --follow-parent + " + test_debug 'gitk --all &' test_done From 97f6987afaae239f7e3ae3944e0b29343b43a894 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 25 Jan 2007 11:53:13 -0800 Subject: [PATCH 096/201] git-svn: avoid tracking change-less revisions They simply aren't interesting to track, and this will allow us to avoid get_log(). Since r0 is covered by this, we need to update the tests to not rely on r0 (which is always empty). Signed-off-by: Eric Wong --- git-svn.perl | 61 +++++++++++++++++++++----------------- t/t9100-git-svn-basic.sh | 2 -- t/t9107-git-svn-migrate.sh | 14 +++++---- 3 files changed, 42 insertions(+), 35 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index a19afb83fb..6ff3a8c5c5 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -315,7 +315,7 @@ sub cmd_set_tree { my $gs = Git::SVN->new; my ($r_last, $cmt_last) = $gs->last_rev_commit; $gs->fetch; - if ($r_last != $gs->{last_rev}) { + if (defined $gs->{last_rev} && $r_last != $gs->{last_rev}) { fatal "There are new revisions that were fetched ", "and need to be merged (or acknowledged) ", "before committing.\nlast rev: $r_last\n", @@ -1214,50 +1214,46 @@ sub do_fetch { $self->make_log_entry($rev, \@parents, $ed); } -sub write_untracked { - my ($self, $rev, $fh, $untracked) = @_; - my $h; - print $fh "r$rev\n" or croak $!; - $h = $untracked->{empty}; +sub get_untracked { + my ($self, $ed) = @_; + my @out; + my $h = $ed->{empty}; foreach (sort keys %$h) { my $act = $h->{$_} ? '+empty_dir' : '-empty_dir'; - print $fh " $act: ", uri_encode($_), "\n" or croak $!; + push @out, " $act: " . uri_encode($_); warn "W: $act: $_\n"; } foreach my $t (qw/dir_prop file_prop/) { - $h = $untracked->{$t} or next; + $h = $ed->{$t} or next; foreach my $path (sort keys %$h) { my $ppath = $path eq '' ? '.' : $path; foreach my $prop (sort keys %{$h->{$path}}) { next if $SKIP_PROP{$prop}; my $v = $h->{$path}->{$prop}; + my $t_ppath_prop = "$t: " . + uri_encode($ppath) . ' ' . + uri_encode($prop); if (defined $v) { - print $fh " +$t: ", - uri_encode($ppath), ' ', - uri_encode($prop), ' ', - uri_encode($v), "\n" - or croak $!; + push @out, " +$t_ppath_prop " . + uri_encode($v); } else { - print $fh " -$t: ", - uri_encode($ppath), ' ', - uri_encode($prop), "\n" - or croak $!; + push @out, " -$t_ppath_prop"; } } } } foreach my $t (qw/absent_file absent_directory/) { - $h = $untracked->{$t} or next; + $h = $ed->{$t} or next; foreach my $parent (sort keys %$h) { foreach my $path (sort @{$h->{$parent}}) { - print $fh " $t: ", - uri_encode("$parent/$path"), "\n" - or croak $!; + push @out, " $t: " . + uri_encode("$parent/$path"); warn "W: $t: $parent/$path ", "Insufficient permissions?\n"; } } } + \@out; } sub parse_svn_date { @@ -1280,12 +1276,15 @@ sub check_author { } sub make_log_entry { - my ($self, $rev, $parents, $untracked) = @_; - my $rp = $self->ra->rev_proplist($rev); - my %log_entry = ( parents => $parents || [], revision => $rev, - revprops => $rp, log => ''); + my ($self, $rev, $parents, $ed) = @_; + my $untracked = $self->get_untracked($ed); + open my $un, '>>', "$self->{dir}/unhandled.log" or croak $!; - $self->write_untracked($rev, $un, $untracked); + print $un "r$rev\n" or croak $!; + print $un $_, "\n" foreach @$untracked; + my %log_entry = ( parents => $parents || [], revision => $rev, + log => ''); + my $rp = $self->ra->rev_proplist($rev); foreach (sort keys %$rp) { my $v = $rp->{$_}; if (/^svn:(author|date|log)$/) { @@ -1296,6 +1295,11 @@ sub make_log_entry { } } close $un or croak $!; + + delete $rp->{'svn:date'}; # this is the only revprop for r0 + return undef if ($ed->{nr} == 0 && scalar @$untracked == 0 && + scalar keys %$rp == 0); + $log_entry{date} = parse_svn_date($log_entry{date}); $log_entry{author} = check_author($log_entry{author}); $log_entry{log} .= "\n"; @@ -1320,8 +1324,9 @@ sub fetch { my ($paths, $rev, $author, $date, $log) = @_; push @revs, [ $paths, $rev ] }); foreach (@revs) { - my $log_entry = $self->do_fetch(@$_); - $self->do_git_commit($log_entry, @parents); + if (my $log_entry = $self->do_fetch(@$_)) { + $self->do_git_commit($log_entry, @parents); + } } last if $max >= $head; $min = $max + 1; diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index 97798c4d07..5355243b92 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -211,8 +211,6 @@ tree d667270a1f7b109f5eb3aaea21ede14b56bfdd6e tree 8f51f74cf0163afc9ad68a4b1537288c4558b5a4 EOF -echo tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 >> expected - test_expect_success "$name" "diff -u a expected" test_expect_failure 'exit if remote refs are ambigious' " diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index 74a45ec647..f6d84ba7a5 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -5,13 +5,17 @@ test_description='git-svn metadata migrations from previous versions' test_expect_success 'setup old-looking metadata' " cp $GIT_DIR/config $GIT_DIR/config-old-git-svn && + mkdir import && + cd import + for i in trunk branches/a branches/b \ + tags/0.1 tags/0.2 tags/0.3; do + mkdir -p \$i && \ + echo hello >> \$i/README || exit 1 + done && \ + svn import -m test . $svnrepo + cd .. && git-svn init $svnrepo && git-svn fetch && - for i in trunk branches/a branches/b tags/0.1 tags/0.2 tags/0.3; do - mkdir -p \$i && echo hello >> \$i/README || exit 1; done && - git ls-files -o trunk branches tags | git update-index --add --stdin && - git commit -m 'test' && - git-svn dcommit && mv $GIT_DIR/svn/* $GIT_DIR/ && rmdir $GIT_DIR/svn && git-update-ref refs/heads/git-svn-HEAD refs/remotes/git-svn && From e5a0b240fc237af6165b728ae9c79288ef624d3b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 25 Jan 2007 15:44:54 -0800 Subject: [PATCH 097/201] git-svn: correctly track revisions made to deleted branches git-svn has never been able to handle deleted branches very well because svn_ra_get_log() is all-or-nothing, meaning that if the max revision passed to it does not contain the path we're tracking, we miss all the revisions in the repository. Branches fetched using --follow-parent still do this sub-optimally (will be fixed soon). --follow-parent will soon become the default, so we will assume that when using get_log(); We will also avoid tracking revprops for revisions with no path-related changes since otherwise we just end up pulling logs to paths we don't care about. Also added a test for this to t9104-git-svn-follow-parent.sh and correctly commit the log message in the preceeding test (which conflicted with a filename). Signed-off-by: Eric Wong --- git-svn.perl | 46 +++++++++++++++++--------------- t/t9104-git-svn-follow-parent.sh | 11 +++++++- 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 6ff3a8c5c5..0e2348af35 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1100,6 +1100,11 @@ sub revisions_eq { sub find_parent_branch { my ($self, $paths, $rev) = @_; return undef unless $::_follow_parent; + unless (defined $paths) { + $self->ra->get_log([''], $rev, $rev, 0, 1, 1, + sub { $paths = $_[0] }); + } + return undef unless defined $paths; # look for a parent from another branch: my @b_path_components = split m#/#, $self->rel_path; @@ -1146,11 +1151,11 @@ sub find_parent_branch { } my ($r0, $parent) = $gs->find_rev_before($r, 1); if ($::_follow_parent && (!defined $r0 || !defined $parent)) { - $gs->ra->get_log([$gs->{path}], 0, $r, 0, 1, 1, sub { - my ($paths, $rev) = @_; - my $log_entry = eval { $gs->do_fetch($paths, $rev) }; - $gs->do_git_commit($log_entry) if $log_entry; - }); + foreach (1 .. $r) { + if (my $log_entry = $gs->do_fetch(undef, $_)) { + $gs->do_git_commit($log_entry); + } + } ($r0, $parent) = $gs->last_rev_commit; } if (defined $r0 && defined $parent && $gs->revisions_eq($r0, $r)) { @@ -1178,16 +1183,8 @@ sub find_parent_branch { } not_found: print STDERR "Branch parent for path: '/", - $self->rel_path, "' not found\n"; - return undef unless $paths; - foreach my $x (sort keys %$paths) { - my $p = $paths->{$x}; - print STDERR ' ', $p->action, ' ', $x; - if (my $cp_from = $p->copyfrom_path) { - print STDERR "(from $cp_from:", $p->copyfrom_rev, ')'; - } - print STDERR "\n"; - } + $self->rel_path, "' @ $rev not found\n"; + print STDERR ' ', $_, "\n" foreach (sort keys %$paths); return undef; } @@ -1279,6 +1276,8 @@ sub make_log_entry { my ($self, $rev, $parents, $ed) = @_; my $untracked = $self->get_untracked($ed); + return undef if ($ed->{nr} == 0 && scalar @$untracked == 0); + open my $un, '>>', "$self->{dir}/unhandled.log" or croak $!; print $un "r$rev\n" or croak $!; print $un $_, "\n" foreach @$untracked; @@ -1296,10 +1295,6 @@ sub make_log_entry { } close $un or croak $!; - delete $rp->{'svn:date'}; # this is the only revprop for r0 - return undef if ($ed->{nr} == 0 && scalar @$untracked == 0 && - scalar keys %$rp == 0); - $log_entry{date} = parse_svn_date($log_entry{date}); $log_entry{author} = check_author($log_entry{author}); $log_entry{log} .= "\n"; @@ -1317,18 +1312,26 @@ sub fetch { my $inc = 1000; my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc); my $err_handler = $SVN::Error::handler; - $SVN::Error::handler = \&skip_unknown_revs; + my $err; + $SVN::Error::handler = sub { ($err) = @_; skip_unknown_revs($err); } ; while (1) { my @revs; $self->ra->get_log([$self->{path}], $min, $max, 0, 1, 1, sub { my ($paths, $rev, $author, $date, $log) = @_; push @revs, [ $paths, $rev ] }); + if (! @revs && $err) { + print STDERR "Branch probably deleted:\n ", + $err->expanded_message, + "\nWill attempt to follow revisions ", + "committed before the deletion\n"; + @revs = map { [ undef, $_ ] } ($min .. $max); + } foreach (@revs) { if (my $log_entry = $self->do_fetch(@$_)) { $self->do_git_commit($log_entry, @parents); } } - last if $max >= $head; + last if $max >= $head || $err; $min = $max + 1; $max += $inc; $max = $head if ($max > $head); @@ -2226,7 +2229,6 @@ sub dup { sub get_log { my ($self, @args) = @_; my $pool = SVN::Pool->new; - $args[4]-- if $args[4] && ! $::_follow_parent; splice(@args, 3, 1) if ($SVN::Core::VERSION le '1.2.0'); my $ret = $self->SUPER::get_log(@args, $pool); $pool->clear; diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 615c863b94..a6ba0faebd 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -85,7 +85,7 @@ test_expect_success 'follow higher-level parent' " cd blob && echo hi > hi && svn add hi && - svn commit -m 'hi' && + svn commit -m 'hihi' && cd .. svn mkdir -m 'new glob at top level' $svnrepo/glob && svn mv -m 'move blob down a level' $svnrepo/blob $svnrepo/glob/blob && @@ -93,6 +93,15 @@ test_expect_success 'follow higher-level parent' " git-svn fetch -i blob --follow-parent " +test_expect_success 'follow deleted directory' " + svn mv -m 'bye!' $svnrepo/glob/blob/hi $svnrepo/glob/blob/bye&& + svn rm -m 'remove glob' $svnrepo/glob && + git-svn init -i glob $svnrepo/glob && + git-svn fetch -i glob && + test \"\`git cat-file blob refs/remotes/glob~1:blob/bye\`\" = hi && + test -z \"\`git ls-tree -z refs/remotes/glob\`\" + " + test_debug 'gitk --all &' test_done From 3ebe8df7f690281c21e330eec156098c14f4e685 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 25 Jan 2007 17:35:40 -0800 Subject: [PATCH 098/201] git-svn: fix segfaults from accessing svn_log_changed_path_t svn_log_changed_path_t structs were being used out of scope outside of svn_ra_get_log (because I wanted to eventually be able to use git-svn with only a single connection to the repository). So now we dup them into a hash. This was fixed while making --follow-parent fetches more efficient. I've moved parsing of the command-line --revision argument outside of the Git::SVN module so Git::SVN::fetch() can be used in more places (such as find_parent_branch). Signed-off-by: Eric Wong --- git-svn.perl | 104 ++++++++++++++++++++----------- t/t9104-git-svn-follow-parent.sh | 1 - 2 files changed, 68 insertions(+), 37 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 0e2348af35..4c9ef7fe15 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -283,7 +283,7 @@ sub cmd_fetch { instead.\n"; } my $gs = Git::SVN->new; - $gs->fetch; + $gs->fetch(parse_revision_argument()); if ($gs->{last_commit} && !verify_ref('refs/heads/master^0')) { command_noisy(qw(update-ref refs/heads/master), $gs->{last_commit}); @@ -482,6 +482,18 @@ sub cmd_commit_diff { ########################### utility functions ######################### +sub parse_revision_argument { + if (!defined $_revision || $_revision eq 'BASE:HEAD') { + return (undef, undef); + } + return ($1, $2) if ($_revision =~ /^(\d+):(\d+)$/); + return ($_revision, $_revision) if ($_revision =~ /^\d+$/); + return (undef, $1) if ($_revision =~ /^BASE:(\d+)$/); + return ($1, undef) if ($_revision =~ /^(\d+):HEAD$/); + die "revision argument: $_revision not understood by git-svn\n", + "Try using the command-line svn client instead\n"; +} + sub complete_svn_url { my ($url, $path) = @_; $path =~ s#/+$##; @@ -914,6 +926,9 @@ sub traverse_ignore { } } +sub last_rev { ($_[0]->last_rev_commit)[0] } +sub last_commit { ($_[0]->last_rev_commit)[1] } + # returns the newest SVN revision number and newest commit SHA1 sub last_rev_commit { my ($self) = @_; @@ -951,22 +966,11 @@ sub last_rev_commit { return ($rev, $c); } -sub parse_revision { - my ($self, $base) = @_; - my $head = $self->ra->get_latest_revnum; - if (!defined $::_revision || $::_revision eq 'BASE:HEAD') { - return ($base + 1, $head) if (defined $base); - return (0, $head); - } - return ($1, $2) if ($::_revision =~ /^(\d+):(\d+)$/); - return ($::_revision, $::_revision) if ($::_revision =~ /^\d+$/); - if ($::_revision =~ /^BASE:(\d+)$/) { - return ($base + 1, $1) if (defined $base); - return (0, $head); - } - return ($1, $head) if ($::_revision =~ /^(\d+):HEAD$/); - die "revision argument: $::_revision not understood by git-svn\n", - "Try using the command-line svn client instead\n"; +sub get_fetch_range { + my ($self, $min, $max) = @_; + $max ||= $self->ra->get_latest_revnum; + $min ||= $self->last_rev || 0; + (++$min, $max); } sub tmp_index_do { @@ -1101,8 +1105,8 @@ sub find_parent_branch { my ($self, $paths, $rev) = @_; return undef unless $::_follow_parent; unless (defined $paths) { - $self->ra->get_log([''], $rev, $rev, 0, 1, 1, - sub { $paths = $_[0] }); + $self->ra->get_log([$self->{path}], $rev, $rev, 0, 1, 1, + sub { $paths = dup_changed_paths($_[0]) }); } return undef unless defined $paths; @@ -1116,13 +1120,13 @@ sub find_parent_branch { unshift(@a_path_components, pop(@b_path_components)); } goto not_found unless defined $i; - my $branch_from = $i->copyfrom_path or goto not_found; + my $branch_from = $i->{copyfrom_path} or goto not_found; if (@a_path_components) { print STDERR "branch_from: $branch_from => "; $branch_from .= '/'.join('/', @a_path_components); print STDERR $branch_from, "\n"; } - my $r = $i->copyfrom_rev; + my $r = $i->{copyfrom_rev}; my $repos_root = $self->ra->{repos_root}; my $url = $self->ra->{url}; my $new_url = $repos_root . $branch_from; @@ -1151,11 +1155,7 @@ sub find_parent_branch { } my ($r0, $parent) = $gs->find_rev_before($r, 1); if ($::_follow_parent && (!defined $r0 || !defined $parent)) { - foreach (1 .. $r) { - if (my $log_entry = $gs->do_fetch(undef, $_)) { - $gs->do_git_commit($log_entry); - } - } + $gs->fetch(0, $r); ($r0, $parent) = $gs->last_rev_commit; } if (defined $r0 && defined $parent && $gs->revisions_eq($r0, $r)) { @@ -1183,8 +1183,19 @@ sub find_parent_branch { } not_found: print STDERR "Branch parent for path: '/", - $self->rel_path, "' @ $rev not found\n"; - print STDERR ' ', $_, "\n" foreach (sort keys %$paths); + $self->rel_path, "' @ r$rev not found:\n"; + return undef unless $paths; + print STDERR "Changed paths:\n"; + foreach my $x (sort keys %$paths) { + my $p = $paths->{$x}; + print STDERR "\t$p->{action}\t$x"; + if ($p->{copyfrom_path}) { + print STDERR "(from $p->{copyfrom_path}: ", + "$p->{copyfrom_rev})"; + } + print STDERR "\n"; + } + print STDERR '-'x72, "\n"; return undef; } @@ -1302,9 +1313,9 @@ sub make_log_entry { } sub fetch { - my ($self, @parents) = @_; + my ($self, $min_rev, $max_rev, @parents) = @_; my ($last_rev, $last_commit) = $self->last_rev_commit; - my ($base, $head) = $self->parse_revision($last_rev); + my ($base, $head) = $self->get_fetch_range($min_rev, $max_rev); return if ($base > $head); if (defined $last_commit) { $self->assert_index_clean($last_commit); @@ -1316,13 +1327,16 @@ sub fetch { $SVN::Error::handler = sub { ($err) = @_; skip_unknown_revs($err); } ; while (1) { my @revs; - $self->ra->get_log([$self->{path}], $min, $max, 0, 1, 1, sub { - my ($paths, $rev, $author, $date, $log) = @_; - push @revs, [ $paths, $rev ] }); - if (! @revs && $err) { + $self->ra->get_log([$self->{path}], $min, $max, 0, 1, 1, + sub { + my ($paths, $rev) = @_; + push @revs, [ dup_changed_paths($paths), $rev ]; + }); + if (! @revs && $err && $max >= $head) { print STDERR "Branch probably deleted:\n ", $err->expanded_message, "\nWill attempt to follow revisions ", + "r$min .. r$max", "committed before the deletion\n"; @revs = map { [ undef, $_ ] } ($min .. $max); } @@ -1331,7 +1345,7 @@ sub fetch { $self->do_git_commit($log_entry, @parents); } } - last if $max >= $head || $err; + last if $max >= $head; $min = $max + 1; $max += $inc; $max = $head if ($max > $head); @@ -1347,7 +1361,7 @@ sub set_tree_cb { $log_entry->{author} = $author; $self->do_git_commit($log_entry, "$rev=$tree"); } else { - $self->fetch("$rev=$tree"); + $self->fetch(undef, undef, "$rev=$tree"); } } @@ -1393,6 +1407,24 @@ sub skip_unknown_revs { croak "Error from SVN, ($errno): ", $err->expanded_message,"\n"; } +# svn_log_changed_path_t objects passed to get_log are likely to be +# overwritten even if only the refs are copied to an external variable, +# so we should dup the structures in their entirety. Using an externally +# passed pool (instead of our temporary and quickly cleared pool in +# Git::SVN::Ra) does not help matters at all... +sub dup_changed_paths { + my ($paths) = @_; + return undef unless $paths; + my %ret; + foreach my $p (keys %$paths) { + my $i = $paths->{$p}; + my %s = map { $_ => $i->$_ } + qw/copyfrom_path copyfrom_rev action/; + $ret{$p} = \%s; + } + \%ret; +} + # rev_db: # Tie::File seems to be prone to offset errors if revisions get sparse, # it's not that fast, either. Tie::File is also not in Perl 5.6. So diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index a6ba0faebd..bfb718886f 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -78,7 +78,6 @@ test_expect_success 'follow larger parent' " true " -# This seems to cause segfaults over HTTP... test_expect_success 'follow higher-level parent' " svn mkdir -m 'follow higher-level parent' $svnrepo/blob && svn co $svnrepo/blob blob && From d3a840dc74d2098c31aac1b89093d847e1d33dd8 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 26 Jan 2007 01:32:45 -0800 Subject: [PATCH 099/201] git-svn: fix committing to subdirectories, add tests I broke this part with the URL minimization; since git-svn will now try to connect to the root of the repository and will end up writing files there if it can... Signed-off-by: Eric Wong --- git-svn.perl | 14 ++++++++++---- t/t9100-git-svn-basic.sh | 29 +++++++++++++++++++++++++++++ t/t9105-git-svn-commit-diff.sh | 9 +++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 4c9ef7fe15..1d448e75da 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -354,7 +354,7 @@ sub cmd_dcommit { my $pool = SVN::Pool->new; my %ed_opts = ( r => $last_rev, ra => $ra->dup, - svn_path => $ra->{svn_path} ); + svn_path => $gs->{path} ); my $ed = SVN::Git::Editor->new(\%ed_opts, $ra->get_commit_editor($log, sub { print "Committed r$_[0]\n"; @@ -437,6 +437,7 @@ sub cmd_commit_diff { my $usage = "Usage: $0 commit-diff -r ". " []\n"; fatal($usage) if (!defined $ta || !defined $tb); + my $svn_path; if (!defined $url) { my $gs = eval { Git::SVN->new }; if (!$gs) { @@ -444,6 +445,7 @@ sub cmd_commit_diff { "the command-line\n", $usage); } $url = $gs->{url}; + $svn_path = $gs->{path}; } unless (defined $_revision) { fatal("-r|--revision is a required argument\n", $usage); @@ -459,6 +461,7 @@ sub cmd_commit_diff { $_message ||= get_commit_entry($tb)->{log}; } my $ra ||= Git::SVN::Ra->new($url); + $svn_path ||= $ra->{svn_path}; my $r = $_revision; if ($r eq 'HEAD') { $r = $ra->get_latest_revnum; @@ -468,7 +471,7 @@ sub cmd_commit_diff { my $pool = SVN::Pool->new; my %ed_opts = ( r => $r, ra => $ra->dup, - svn_path => $ra->{svn_path} ); + svn_path => $svn_path ); my $ed = SVN::Git::Editor->new(\%ed_opts, $ra->get_commit_editor($_message, sub { print "Committed r$_[0]\n" }), @@ -1374,7 +1377,7 @@ sub set_tree { my $pool = SVN::Pool->new; my $ed = SVN::Git::Editor->new({ r => $self->{last_rev}, ra => $self->ra->dup, - svn_path => $self->ra->{svn_path} + svn_path => $self->{path} }, $self->ra->get_commit_editor( $log_entry->{log}, sub { @@ -1902,6 +1905,8 @@ sub new { $self->{pool} = SVN::Pool->new; $self->{bat} = { '' => $self->open_root($self->{r}, $self->{pool}) }; $self->{rm} = { }; + $self->{path_prefix} = length $self->{svn_path} ? + "$self->{svn_path}/" : ''; require Digest::MD5; return $self; } @@ -1911,7 +1916,8 @@ sub split_path { } sub repo_path { - (defined $_[1] && length $_[1]) ? $_[1] : '' + my ($self, $path) = @_; + $self->{path_prefix}.(defined $path ? $path : ''); } sub url_path { diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index 5355243b92..3dc4de2fad 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -236,4 +236,33 @@ test_expect_success \ '^:refs/remotes/git-svn$' " +test_expect_success 'able to dcommit to a subdirectory' " + git-svn fetch -i bar && + git checkout -b my-bar refs/remotes/bar && + echo abc > d && + git update-index --add d && + git commit -m '/bar/d should be in the log' && + git-svn dcommit -i bar && + test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\" && + mkdir newdir && + echo new > newdir/dir && + git update-index --add newdir/dir && + git commit -m 'add a new directory' && + git-svn dcommit -i bar && + test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\" && + echo foo >> newdir/dir && + git update-index newdir/dir && + git commit -m 'modify a file in new directory' && + git-svn dcommit -i bar && + test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\" + " + +test_expect_success 'able to set-tree to a subdirectory' " + echo cba > d && + git update-index d && + git commit -m 'update /bar/d' && + git-svn set-tree -i bar HEAD && + test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\" + " + test_done diff --git a/t/t9105-git-svn-commit-diff.sh b/t/t9105-git-svn-commit-diff.sh index 6323c7e3ac..c668dd1270 100755 --- a/t/t9105-git-svn-commit-diff.sh +++ b/t/t9105-git-svn-commit-diff.sh @@ -31,4 +31,13 @@ test_expect_success 'test the commit-diff command' " cmp readme wc/readme " +test_expect_success 'commit-diff to a sub-directory (with git-svn config)' " + svn import -m 'sub-directory' import $svnrepo/subdir && + git-svn init $svnrepo/subdir && + git-svn fetch && + git-svn commit-diff -r3 '$prev' '$head' && + svn cat $svnrepo/subdir/readme > readme.2 && + cmp readme readme.2 + " + test_done From 6e8548cca888205a99773a18a73533b2b8dc651d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 27 Jan 2007 01:32:00 -0800 Subject: [PATCH 100/201] git-svn: avoid an extra svn_ra connection during commits Before, we needed a separate svn_ra instance to run our check_path calls once the editor was active; but we can avoid that by running all the check_path calls before our editor is active. Signed-off-by: Eric Wong --- git-svn.perl | 205 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 123 insertions(+), 82 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 1d448e75da..e771bf5692 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -352,16 +352,22 @@ sub cmd_dcommit { my $log = get_commit_entry($d)->{log}; my $ra = $gs->ra; my $pool = SVN::Pool->new; + my $mods = generate_diff("$d~1", $d); + my $types = check_diff_paths($ra, + $gs->{path}, + $last_rev, + $mods); my %ed_opts = ( r => $last_rev, - ra => $ra->dup, + mods => $mods, + url => $ra->{url}, + types => $types, svn_path => $gs->{path} ); my $ed = SVN::Git::Editor->new(\%ed_opts, $ra->get_commit_editor($log, sub { print "Committed r$_[0]\n"; $last_rev = $_[0]; }), $pool); - my $mods = $ed->apply_diff("$d~1", $d); - if (@$mods == 0) { + if (!$ed->apply_diff($mods, $d)) { print "No changes\n$d~1 == $d\n"; } } @@ -469,15 +475,15 @@ sub cmd_commit_diff { die "revision argument: $r not understood by git-svn\n"; } my $pool = SVN::Pool->new; - my %ed_opts = ( r => $r, - ra => $ra->dup, - svn_path => $svn_path ); + my $mods = generate_diff($ta, $tb); + my $types = check_diff_paths($ra, $svn_path, $r, $mods); + my %ed_opts = ( r => $r, url => $ra->{url}, svn_path => $svn_path, + mods => $mods, types => $types ); my $ed = SVN::Git::Editor->new(\%ed_opts, $ra->get_commit_editor($_message, sub { print "Committed r$_[0]\n" }), $pool); - my $mods = $ed->apply_diff($ta, $tb); - if (@$mods == 0) { + if (!$ed->apply_diff($mods, $tb)) { print "No changes\n$ta == $tb\n"; } $pool->clear; @@ -708,6 +714,92 @@ sub tz_to_s_offset { return ($1 * 60) + ($tz * 3600); } +sub generate_diff { + my ($tree_a, $tree_b) = @_; + my @diff_tree = qw(diff-tree -z -r); + if ($_cp_similarity) { + push @diff_tree, "-C$_cp_similarity"; + } else { + push @diff_tree, '-C'; + } + push @diff_tree, '--find-copies-harder' if $_find_copies_harder; + push @diff_tree, "-l$_l" if defined $_l; + push @diff_tree, $tree_a, $tree_b; + my ($diff_fh, $ctx) = command_output_pipe(@diff_tree); + local $/ = "\0"; + my $state = 'meta'; + my @mods; + while (<$diff_fh>) { + chomp $_; # this gets rid of the trailing "\0" + 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, + sha1_b => $3, chg => $4 }; + if ($4 =~ /^(?:C|R)$/) { + $state = 'file_a'; + } else { + $state = 'file_b'; + } + } elsif ($state eq 'file_a') { + my $x = $mods[$#mods] or croak "Empty array\n"; + if ($x->{chg} !~ /^(?:C|R)$/) { + croak "Error parsing $_, $x->{chg}\n"; + } + $x->{file_a} = $_; + $state = 'file_b'; + } elsif ($state eq 'file_b') { + my $x = $mods[$#mods] or croak "Empty array\n"; + if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) { + croak "Error parsing $_, $x->{chg}\n"; + } + if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) { + croak "Error parsing $_, $x->{chg}\n"; + } + $x->{file_b} = $_; + $state = 'meta'; + } else { + croak "Error parsing $_\n"; + } + } + command_close_pipe($diff_fh, $ctx); + \@mods; +} + +sub check_diff_paths { + my ($ra, $pfx, $rev, $mods) = @_; + my %types; + $pfx .= '/' if length $pfx; + + sub type_diff_paths { + my ($ra, $types, $path, $rev) = @_; + my @p = split m#/+#, $path; + my $c = shift @p; + unless (defined $types->{$c}) { + $types->{$c} = $ra->check_path($c, $rev); + } + while (@p) { + $c .= '/' . shift @p; + next if defined $types->{$c}; + $types->{$c} = $ra->check_path($c, $rev); + } + } + + foreach my $m (@$mods) { + foreach my $f (qw/file_a file_b/) { + next unless defined $m->{$f}; + my ($dir) = ($m->{$f} =~ m#^(.*?)/?(?:[^/]+)$#); + if (length $pfx.$dir && ! defined $types{$dir}) { + type_diff_paths($ra, \%types, $pfx.$dir, $rev); + } + } + } + use Data::Dumper; + warn Dumper \%types; + warn Dumper $mods; + \%types; +} + package Git::SVN; use strict; use warnings; @@ -1375,18 +1467,20 @@ sub set_tree { fatal("Must have an existing revision to commit\n"); } my $pool = SVN::Pool->new; - my $ed = SVN::Git::Editor->new({ r => $self->{last_rev}, - ra => $self->ra->dup, - svn_path => $self->{path} - }, + my $mods = ::generate_diff($self->{last_commit}, $tree); + my $types = ::check_diff_paths($self->ra, $self->{path}, + $self->{last_rev}, $mods); + my %ed_opts = ( r => $self->{last_rev}, url => $self->ra->{url}, + svn_path => $self->{path}, + mods => $mods, types => $types ); + my $ed = SVN::Git::Editor->new(\%ed_opts, $self->ra->get_commit_editor( $log_entry->{log}, sub { $self->set_tree_cb($log_entry, $tree, @_); }), $pool); - my $mods = $ed->apply_diff($self->{last_commit}, $tree); - if (@$mods == 0) { + if (!$ed->apply_diff($mods, $tree)) { print "No changes\nr$self->{last_rev} = $tree\n"; } $pool->clear; @@ -1898,7 +1992,7 @@ sub new { my $git_svn = shift; my $self = SVN::Delta::Editor->new(@_); bless $self, $class; - foreach (qw/svn_path r ra/) { + foreach (qw/svn_path mods url types r/) { die "$_ required!\n" unless (defined $git_svn->{$_}); $self->{$_} = $git_svn->{$_}; } @@ -1922,7 +2016,7 @@ sub repo_path { sub url_path { my ($self, $path) = @_; - $self->{ra}->{url} . '/' . $self->repo_path($path); + $self->{url} . '/' . $self->repo_path($path); } sub rmdirs { @@ -1972,7 +2066,10 @@ sub rmdirs { sub open_or_add_dir { my ($self, $full_path, $baton) = @_; - my $t = $self->{ra}->check_path($full_path, $self->{r}); + my $t = $self->{types}->{$full_path}; + if (!defined $t) { + die "$full_path not known in r$self->{r} or we have a bug!\n"; + } if ($t == $SVN::Node::none) { return $self->add_directory($full_path, $baton, undef, -1, $self->{pool}); @@ -1989,9 +2086,9 @@ sub open_or_add_dir { sub ensure_path { my ($self, $path) = @_; my $bat = $self->{bat}; - $path = $self->repo_path($path); - return $bat->{''} unless (length $path); - my @p = split m#/+#, $path; + my $repo_path = $self->repo_path($path); + return $bat->{''} unless (length $repo_path); + my @p = split m#/+#, $repo_path; my $c = shift @p; $bat->{$c} ||= $self->open_or_add_dir($c, $bat->{''}); while (@p) { @@ -2129,59 +2226,9 @@ sub abort_edit { # this drives the editor sub apply_diff { - my ($self, $tree_a, $tree_b) = @_; - my @diff_tree = qw(diff-tree -z -r); - if ($::_cp_similarity) { - push @diff_tree, "-C$::_cp_similarity"; - } else { - push @diff_tree, '-C'; - } - push @diff_tree, '--find-copies-harder' if $::_find_copies_harder; - push @diff_tree, "-l$::_l" if defined $::_l; - push @diff_tree, $tree_a, $tree_b; - my ($diff_fh, $ctx) = command_output_pipe(@diff_tree); - my $nl = $/; - local $/ = "\0"; - my $state = 'meta'; - my @mods; - while (<$diff_fh>) { - chomp $_; # this gets rid of the trailing "\0" - 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, - sha1_b => $3, chg => $4 }; - if ($4 =~ /^(?:C|R)$/) { - $state = 'file_a'; - } else { - $state = 'file_b'; - } - } elsif ($state eq 'file_a') { - my $x = $mods[$#mods] or croak "Empty array\n"; - if ($x->{chg} !~ /^(?:C|R)$/) { - croak "Error parsing $_, $x->{chg}\n"; - } - $x->{file_a} = $_; - $state = 'file_b'; - } elsif ($state eq 'file_b') { - my $x = $mods[$#mods] or croak "Empty array\n"; - if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) { - croak "Error parsing $_, $x->{chg}\n"; - } - if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) { - croak "Error parsing $_, $x->{chg}\n"; - } - $x->{file_b} = $_; - $state = 'meta'; - } else { - croak "Error parsing $_\n"; - } - } - command_close_pipe($diff_fh, $ctx); - $/ = $nl; - + my ($self, $mods, $tree_b) = @_; 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) { + foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) { my $f = $m->{chg}; if (defined $o{$f}) { $self->$f($m); @@ -2190,12 +2237,12 @@ sub apply_diff { } } $self->rmdirs($tree_b) if $::_rmdir; - if (@mods == 0) { + if (@$mods == 0) { $self->abort_edit; } else { $self->close_edit; } - \@mods; + return scalar @$mods; } package Git::SVN::Ra; @@ -2256,14 +2303,6 @@ sub DESTROY { # do not call the real DESTROY since we store ourselves in %RA } -sub dup { - my ($self) = @_; - my $dup = SVN::Ra->new(pool => SVN::Pool->new, - map { $_ => $self->{$_} } qw/config url - auth auth_provider_callbacks repos_root svn_path/); - bless $dup, ref $self; -} - sub get_log { my ($self, @args) = @_; my $pool = SVN::Pool->new; @@ -2922,6 +2961,8 @@ sub DESTROY { author => 'committer name' }; + +# this is generated by generate_diff(); @mods = array of diff-index line hashes, each element represents one line of diff-index output From 6139535436d7ec9808fca75821f14d4d5061f343 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 27 Jan 2007 14:33:08 -0800 Subject: [PATCH 101/201] git-svn: simplify usage of the SVN::Git::Editor interface Signed-off-by: Eric Wong --- git-svn.perl | 283 +++++++++++++++++++++++++-------------------------- 1 file changed, 140 insertions(+), 143 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index e771bf5692..4f7ebaf58b 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -349,25 +349,16 @@ sub cmd_dcommit { if ($_dry_run) { print "diff-tree $d~1 $d\n"; } else { - my $log = get_commit_entry($d)->{log}; - my $ra = $gs->ra; - my $pool = SVN::Pool->new; - my $mods = generate_diff("$d~1", $d); - my $types = check_diff_paths($ra, - $gs->{path}, - $last_rev, - $mods); my %ed_opts = ( r => $last_rev, - mods => $mods, - url => $ra->{url}, - types => $types, + log => get_commit_entry($d)->{log}, + ra => $gs->ra, + tree_a => "$d~1", + tree_b => $d, + editor_cb => sub { + print "Committed r$_[0]\n"; + $last_rev = $_[0]; }, svn_path => $gs->{path} ); - my $ed = SVN::Git::Editor->new(\%ed_opts, - $ra->get_commit_editor($log, - sub { print "Committed r$_[0]\n"; - $last_rev = $_[0]; }), - $pool); - if (!$ed->apply_diff($mods, $d)) { + if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) { print "No changes\n$d~1 == $d\n"; } } @@ -474,19 +465,16 @@ sub cmd_commit_diff { } elsif ($r !~ /^\d+$/) { die "revision argument: $r not understood by git-svn\n"; } - my $pool = SVN::Pool->new; - my $mods = generate_diff($ta, $tb); - my $types = check_diff_paths($ra, $svn_path, $r, $mods); - my %ed_opts = ( r => $r, url => $ra->{url}, svn_path => $svn_path, - mods => $mods, types => $types ); - my $ed = SVN::Git::Editor->new(\%ed_opts, - $ra->get_commit_editor($_message, - sub { print "Committed r$_[0]\n" }), - $pool); - if (!$ed->apply_diff($mods, $tb)) { + my %ed_opts = ( r => $r, + log => $_message, + ra => $ra, + tree_a => $ta, + tree_b => $tb, + editor_cb => sub { print "Committed r$_[0]\n" }, + svn_path => $svn_path ); + if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) { print "No changes\n$ta == $tb\n"; } - $pool->clear; } ########################### utility functions ######################### @@ -714,92 +702,6 @@ sub tz_to_s_offset { return ($1 * 60) + ($tz * 3600); } -sub generate_diff { - my ($tree_a, $tree_b) = @_; - my @diff_tree = qw(diff-tree -z -r); - if ($_cp_similarity) { - push @diff_tree, "-C$_cp_similarity"; - } else { - push @diff_tree, '-C'; - } - push @diff_tree, '--find-copies-harder' if $_find_copies_harder; - push @diff_tree, "-l$_l" if defined $_l; - push @diff_tree, $tree_a, $tree_b; - my ($diff_fh, $ctx) = command_output_pipe(@diff_tree); - local $/ = "\0"; - my $state = 'meta'; - my @mods; - while (<$diff_fh>) { - chomp $_; # this gets rid of the trailing "\0" - 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, - sha1_b => $3, chg => $4 }; - if ($4 =~ /^(?:C|R)$/) { - $state = 'file_a'; - } else { - $state = 'file_b'; - } - } elsif ($state eq 'file_a') { - my $x = $mods[$#mods] or croak "Empty array\n"; - if ($x->{chg} !~ /^(?:C|R)$/) { - croak "Error parsing $_, $x->{chg}\n"; - } - $x->{file_a} = $_; - $state = 'file_b'; - } elsif ($state eq 'file_b') { - my $x = $mods[$#mods] or croak "Empty array\n"; - if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) { - croak "Error parsing $_, $x->{chg}\n"; - } - if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) { - croak "Error parsing $_, $x->{chg}\n"; - } - $x->{file_b} = $_; - $state = 'meta'; - } else { - croak "Error parsing $_\n"; - } - } - command_close_pipe($diff_fh, $ctx); - \@mods; -} - -sub check_diff_paths { - my ($ra, $pfx, $rev, $mods) = @_; - my %types; - $pfx .= '/' if length $pfx; - - sub type_diff_paths { - my ($ra, $types, $path, $rev) = @_; - my @p = split m#/+#, $path; - my $c = shift @p; - unless (defined $types->{$c}) { - $types->{$c} = $ra->check_path($c, $rev); - } - while (@p) { - $c .= '/' . shift @p; - next if defined $types->{$c}; - $types->{$c} = $ra->check_path($c, $rev); - } - } - - foreach my $m (@$mods) { - foreach my $f (qw/file_a file_b/) { - next unless defined $m->{$f}; - my ($dir) = ($m->{$f} =~ m#^(.*?)/?(?:[^/]+)$#); - if (length $pfx.$dir && ! defined $types{$dir}) { - type_diff_paths($ra, \%types, $pfx.$dir, $rev); - } - } - } - use Data::Dumper; - warn Dumper \%types; - warn Dumper $mods; - \%types; -} - package Git::SVN; use strict; use warnings; @@ -1466,24 +1368,17 @@ sub set_tree { unless ($self->{last_rev}) { fatal("Must have an existing revision to commit\n"); } - my $pool = SVN::Pool->new; - my $mods = ::generate_diff($self->{last_commit}, $tree); - my $types = ::check_diff_paths($self->ra, $self->{path}, - $self->{last_rev}, $mods); - my %ed_opts = ( r => $self->{last_rev}, url => $self->ra->{url}, - svn_path => $self->{path}, - mods => $mods, types => $types ); - my $ed = SVN::Git::Editor->new(\%ed_opts, - $self->ra->get_commit_editor( - $log_entry->{log}, sub { - $self->set_tree_cb($log_entry, - $tree, @_); - }), - $pool); - if (!$ed->apply_diff($mods, $tree)) { + my %ed_opts = ( r => $self->{last_rev}, + log => $log_entry->{log}, + ra => $self->ra, + tree_a => $self->{last_commit}, + tree_b => $tree, + editor_cb => sub { + $self->set_tree_cb($log_entry, $tree, @_) }, + svn_path => $self->{path} ); + if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) { print "No changes\nr$self->{last_rev} = $tree\n"; } - $pool->clear; } sub skip_unknown_revs { @@ -1988,15 +1883,28 @@ package SVN::Git::Editor; use IO::File; sub new { - my $class = shift; - my $git_svn = shift; - my $self = SVN::Delta::Editor->new(@_); - bless $self, $class; - foreach (qw/svn_path mods url types r/) { - die "$_ required!\n" unless (defined $git_svn->{$_}); - $self->{$_} = $git_svn->{$_}; + my ($class, $opts) = @_; + foreach (qw/svn_path r ra tree_a tree_b log editor_cb/) { + die "$_ required!\n" unless (defined $opts->{$_}); } - $self->{pool} = SVN::Pool->new; + + my $pool = SVN::Pool->new; + my $mods = generate_diff($opts->{tree_a}, $opts->{tree_b}); + my $types = check_diff_paths($opts->{ra}, $opts->{svn_path}, + $opts->{r}, $mods); + + # $opts->{ra} functions should not be used after this: + my @ce = $opts->{ra}->get_commit_editor($opts->{log}, + $opts->{editor_cb}, $pool); + my $self = SVN::Delta::Editor->new(@ce, $pool); + bless $self, $class; + foreach (qw/svn_path r tree_a tree_b/) { + $self->{$_} = $opts->{$_}; + } + $self->{url} = $opts->{ra}->{url}; + $self->{mods} = $mods; + $self->{types} = $types; + $self->{pool} = $pool; $self->{bat} = { '' => $self->open_root($self->{r}, $self->{pool}) }; $self->{rm} = { }; $self->{path_prefix} = length $self->{svn_path} ? @@ -2005,6 +1913,89 @@ sub new { return $self; } +sub generate_diff { + my ($tree_a, $tree_b) = @_; + my @diff_tree = qw(diff-tree -z -r); + if ($::_cp_similarity) { + push @diff_tree, "-C$::_cp_similarity"; + } else { + push @diff_tree, '-C'; + } + push @diff_tree, '--find-copies-harder' if $::_find_copies_harder; + push @diff_tree, "-l$::_l" if defined $::_l; + push @diff_tree, $tree_a, $tree_b; + my ($diff_fh, $ctx) = command_output_pipe(@diff_tree); + local $/ = "\0"; + my $state = 'meta'; + my @mods; + while (<$diff_fh>) { + chomp $_; # this gets rid of the trailing "\0" + 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, + sha1_b => $3, chg => $4 }; + if ($4 =~ /^(?:C|R)$/) { + $state = 'file_a'; + } else { + $state = 'file_b'; + } + } elsif ($state eq 'file_a') { + my $x = $mods[$#mods] or croak "Empty array\n"; + if ($x->{chg} !~ /^(?:C|R)$/) { + croak "Error parsing $_, $x->{chg}\n"; + } + $x->{file_a} = $_; + $state = 'file_b'; + } elsif ($state eq 'file_b') { + my $x = $mods[$#mods] or croak "Empty array\n"; + if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) { + croak "Error parsing $_, $x->{chg}\n"; + } + if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) { + croak "Error parsing $_, $x->{chg}\n"; + } + $x->{file_b} = $_; + $state = 'meta'; + } else { + croak "Error parsing $_\n"; + } + } + command_close_pipe($diff_fh, $ctx); + \@mods; +} + +sub check_diff_paths { + my ($ra, $pfx, $rev, $mods) = @_; + my %types; + $pfx .= '/' if length $pfx; + + sub type_diff_paths { + my ($ra, $types, $path, $rev) = @_; + my @p = split m#/+#, $path; + my $c = shift @p; + unless (defined $types->{$c}) { + $types->{$c} = $ra->check_path($c, $rev); + } + while (@p) { + $c .= '/' . shift @p; + next if defined $types->{$c}; + $types->{$c} = $ra->check_path($c, $rev); + } + } + + foreach my $m (@$mods) { + foreach my $f (qw/file_a file_b/) { + next unless defined $m->{$f}; + my ($dir) = ($m->{$f} =~ m#^(.*?)/?(?:[^/]+)$#); + if (length $pfx.$dir && ! defined $types{$dir}) { + type_diff_paths($ra, \%types, $pfx.$dir, $rev); + } + } + } + \%types; +} + sub split_path { return ($_[0] =~ m#^(.*?)/?([^/]+)$#); } @@ -2020,7 +2011,7 @@ sub url_path { } sub rmdirs { - my ($self, $tree_b) = @_; + my ($self) = @_; my $rm = $self->{rm}; delete $rm->{''}; # we never delete the url we're tracking return unless %$rm; @@ -2038,8 +2029,8 @@ sub rmdirs { delete $rm->{''}; # we never delete the url we're tracking return unless %$rm; - my ($fh, $ctx) = command_output_pipe( - qw/ls-tree --name-only -r -z/, $tree_b); + my ($fh, $ctx) = command_output_pipe(qw/ls-tree --name-only -r -z/, + $self->{tree_b}); local $/ = "\0"; while (<$fh>) { chomp; @@ -2221,12 +2212,18 @@ sub close_edit { sub abort_edit { my ($self) = @_; $self->SUPER::abort_edit($self->{pool}); +} + +sub DESTROY { + my $self = shift; + $self->SUPER::DESTROY(@_); $self->{pool}->clear; } # this drives the editor sub apply_diff { - my ($self, $mods, $tree_b) = @_; + my ($self) = @_; + my $mods = $self->{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) { my $f = $m->{chg}; @@ -2236,7 +2233,7 @@ sub apply_diff { fatal("Invalid change type: $f\n"); } } - $self->rmdirs($tree_b) if $::_rmdir; + $self->rmdirs if $::_rmdir; if (@$mods == 0) { $self->abort_edit; } else { From 21819a370839fdae818975967cef384510e4a8cd Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 27 Jan 2007 14:38:10 -0800 Subject: [PATCH 102/201] git-svn: cleanup remove unused function Also move tz_to_s_offset into Git::SVN::Log since that's the only place it's used now. Signed-off-by: Eric Wong --- git-svn.perl | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 4f7ebaf58b..7249d6f417 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -679,29 +679,6 @@ sub cmt_metadata { command(qw/cat-file commit/, shift)))[-1]); } -sub get_commit_time { - my $cmt = shift; - my $fh = command_output_pipe(qw/rev-list --pretty=raw -n1/, $cmt); - while (<$fh>) { - /^committer\s(?:.+) (\d+) ([\-\+]?\d+)$/ or next; - my ($s, $tz) = ($1, $2); - if ($tz =~ s/^\+//) { - $s += tz_to_s_offset($tz); - } elsif ($tz =~ s/^\-//) { - $s -= tz_to_s_offset($tz); - } - close $fh; - return $s; - } - die "Can't get commit time for commit: $cmt\n"; -} - -sub tz_to_s_offset { - my ($tz) = @_; - $tz =~ s/(\d\d)$//; - return ($1 * 60) + ($tz * 3600); -} - package Git::SVN; use strict; use warnings; @@ -2496,6 +2473,12 @@ sub run_pager { exec $pager or ::fatal "Can't run pager: $! ($pager)\n"; } +sub tz_to_s_offset { + my ($tz) = @_; + $tz =~ s/(\d\d)$//; + return ($1 * 60) + ($tz * 3600); +} + sub get_author_info { my ($dest, $author, $t, $tz) = @_; $author =~ s/(?:^\s*|\s*$)//g; @@ -2512,9 +2495,9 @@ sub get_author_info { $dest->{a} = $au; # Date::Parse isn't in the standard Perl distro :( if ($tz =~ s/^\+//) { - $t += ::tz_to_s_offset($tz); + $t += tz_to_s_offset($tz); } elsif ($tz =~ s/^\-//) { - $t -= ::tz_to_s_offset($tz); + $t -= tz_to_s_offset($tz); } $dest->{t_utc} = $t; } From 0af9c9f94ae8a327536679ec1976df65ecd64b6e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 27 Jan 2007 22:28:56 -0800 Subject: [PATCH 103/201] git-svn: allow multi-fetch to fetch things chronologically Since single fetching is a special case of multi-fetch, share code with it and the fetch loop into Git::SVN::Ra since it uses a single Ra connection and multiple Git::SVN objects. Signed-off-by: Eric Wong --- git-svn.perl | 211 +++++++++++++++++++++++++++++---------------------- 1 file changed, 120 insertions(+), 91 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 7249d6f417..5d398ee65f 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -416,15 +416,11 @@ sub cmd_multi_init { } sub cmd_multi_fetch { - my @gs; - foreach (command(qw/config -l/)) { - next unless m!^svn-remote\.(.+)\.fetch= - \s*(.*)\s*:\s*refs/remotes/(.+)\s*$!x; - my ($repo_id, $path, $ref_id) = ($1, $2, $3); - push @gs, Git::SVN->new($ref_id, $repo_id, $path); - } - foreach (@gs) { - $_->fetch; + my $remotes = Git::SVN::read_all_remotes(); + foreach my $repo_id (sort keys %$remotes) { + my $url = $remotes->{$repo_id}->{url} or next; + my $fetch = $remotes->{$repo_id}->{fetch} or next; + Git::SVN::fetch_all($repo_id, $url, $fetch); } } @@ -698,6 +694,28 @@ BEGIN svn:entry:committed-date/; } +sub fetch_all { + my ($repo_id, $url, $fetch) = @_; + my @gs; + my $ra = Git::SVN::Ra->new($url); + my $head = $ra->get_latest_revnum; + my $base = $head; + my $new_remote; + foreach my $p (sort keys %$fetch) { + my $gs = Git::SVN->new($fetch->{$p}, $repo_id, $p); + my $lr = $gs->last_rev; + if (defined $lr) { + $base = $lr if ($lr < $base); + } else { + $new_remote = 1; + } + push @gs, $gs; + } + $base = 0 if $new_remote; + return if (++$base > $head); + $ra->gs_fetch_loop_common($base, $head, @gs); +} + sub read_all_remotes { my $r = {}; foreach (grep { s/^svn-remote\.// } command(qw/config -l/)) { @@ -981,16 +999,12 @@ sub assert_index_clean { } sub get_commit_parents { - my ($self, $log_entry, @parents) = @_; + my ($self, $log_entry) = @_; my (%seen, @ret, @tmp); - # commit parents can be conditionally bound to a particular - # svn revision via: "svn_revno=commit_sha1", filter them out here: - foreach my $p (@parents) { - next unless defined $p; - if ($p =~ /^(\d+)=($::sha1_short)$/o) { - push @tmp, $2 if $1 == $log_entry->{revision}; - } else { - push @tmp, $p if $p =~ /^$::sha1_short$/o; + # legacy support for 'set-tree'; this is only used by set_tree_cb: + if (my $ip = $self->{inject_parents}) { + if (my $commit = delete $ip->{$log_entry->{revision}}) { + push @tmp, $commit; } } if (my $cur = ::verify_ref($self->refname.'^0')) { @@ -1017,7 +1031,7 @@ sub full_url { } sub do_git_commit { - my ($self, $log_entry, @parents) = @_; + my ($self, $log_entry) = @_; if (my $c = $self->rev_db_get($log_entry->{revision})) { croak "$log_entry->{revision} = $c already exists! ", "Why are we refetching it?\n"; @@ -1037,7 +1051,7 @@ sub do_git_commit { die "Tree is not a valid sha1: $tree\n" if $tree !~ /^$::sha1$/o; my @exec = ('git-commit-tree', $tree); - foreach ($self->get_commit_parents($log_entry, @parents)) { + foreach ($self->get_commit_parents($log_entry)) { push @exec, '-p', $_; } defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec)) @@ -1291,40 +1305,7 @@ sub fetch { my ($last_rev, $last_commit) = $self->last_rev_commit; my ($base, $head) = $self->get_fetch_range($min_rev, $max_rev); return if ($base > $head); - if (defined $last_commit) { - $self->assert_index_clean($last_commit); - } - my $inc = 1000; - my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc); - my $err_handler = $SVN::Error::handler; - my $err; - $SVN::Error::handler = sub { ($err) = @_; skip_unknown_revs($err); } ; - while (1) { - my @revs; - $self->ra->get_log([$self->{path}], $min, $max, 0, 1, 1, - sub { - my ($paths, $rev) = @_; - push @revs, [ dup_changed_paths($paths), $rev ]; - }); - if (! @revs && $err && $max >= $head) { - print STDERR "Branch probably deleted:\n ", - $err->expanded_message, - "\nWill attempt to follow revisions ", - "r$min .. r$max", - "committed before the deletion\n"; - @revs = map { [ undef, $_ ] } ($min .. $max); - } - foreach (@revs) { - if (my $log_entry = $self->do_fetch(@$_)) { - $self->do_git_commit($log_entry, @parents); - } - } - last if $max >= $head; - $min = $max + 1; - $max += $inc; - $max = $head if ($max > $head); - } - $SVN::Error::handler = $err_handler; + $self->ra->gs_fetch_loop_common($base, $head, $self); } sub set_tree_cb { @@ -1335,7 +1316,8 @@ sub set_tree_cb { $log_entry->{author} = $author; $self->do_git_commit($log_entry, "$rev=$tree"); } else { - $self->fetch(undef, undef, "$rev=$tree"); + $self->{inject_parents} = { $rev => $tree }; + $self->fetch(undef, undef); } } @@ -1358,42 +1340,6 @@ sub set_tree { } } -sub skip_unknown_revs { - my ($err) = @_; - my $errno = $err->apr_err(); - # Maybe the branch we're tracking didn't - # exist when the repo started, so it's - # not an error if it doesn't, just continue - # - # Wonderfully consistent library, eh? - # 160013 - svn:// and file:// - # 175002 - http(s):// - # 175007 - http(s):// (this repo required authorization, too...) - # More codes may be discovered later... - if ($errno == 175007 || $errno == 175002 || $errno == 160013) { - return; - } - croak "Error from SVN, ($errno): ", $err->expanded_message,"\n"; -} - -# svn_log_changed_path_t objects passed to get_log are likely to be -# overwritten even if only the refs are copied to an external variable, -# so we should dup the structures in their entirety. Using an externally -# passed pool (instead of our temporary and quickly cleared pool in -# Git::SVN::Ra) does not help matters at all... -sub dup_changed_paths { - my ($paths) = @_; - return undef unless $paths; - my %ret; - foreach my $p (keys %$paths) { - my $i = $paths->{$p}; - my %s = map { $_ => $i->$_ } - qw/copyfrom_path copyfrom_rev action/; - $ret{$p} = \%s; - } - \%ret; -} - # rev_db: # Tie::File seems to be prone to offset errors if revisions get sparse, # it's not that fast, either. Tie::File is also not in Perl 5.6. So @@ -2324,6 +2270,53 @@ sub gs_do_switch { $editor->{git_commit_ok}; } +sub gs_fetch_loop_common { + my ($self, $base, $head, @gs) = @_; + my $inc = 1000; + my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc); + my $err_handler = $SVN::Error::handler; + my $err; + $SVN::Error::handler = sub { ($err) = @_; skip_unknown_revs($err); }; + my @paths = @gs == 1 ? ($gs[0]->{path}) : (''); + foreach my $gs (@gs) { + if (my $last_commit = $gs->last_commit) { + $gs->assert_index_clean($last_commit); + } + $gs->{path_regex} = qr/^\/\Q$gs->{path}\E\/?/; + } + while (1) { + my @revs; + $self->get_log(\@paths, $min, $max, 0, 1, 1, + sub { push @revs, [ dup_changed_paths($_[0]), $_[1] ]; }); + if (! @revs && $err && $max >= $head) { + print STDERR "Branch probably deleted:\n ", + $err->expanded_message, + "\nWill attempt to follow revisions ", + "r$min .. r$max ", + "committed before the deletion\n"; + @revs = map { [ undef, $_ ] } ($min .. $max); + } + foreach (@revs) { + my ($paths, $r) = @$_; + foreach my $gs (@gs) { + if ($paths) { + grep /$gs->{path_regex}/, keys %$paths + or next; + } + next if defined $gs->rev_db_get($r); + if (my $log_entry = $gs->do_fetch($paths, $r)) { + $gs->do_git_commit($log_entry); + } + } + } + last if $max >= $head; + $min = $max + 1; + $max += $inc; + $max = $head if ($max > $head); + } + $SVN::Error::handler = $err_handler; +} + sub minimize_url { my ($self) = @_; return $self->{url} if ($self->{url} eq $self->{repos_root}); @@ -2356,6 +2349,42 @@ sub can_do_switch { $can_do_switch; } +sub skip_unknown_revs { + my ($err) = @_; + my $errno = $err->apr_err(); + # Maybe the branch we're tracking didn't + # exist when the repo started, so it's + # not an error if it doesn't, just continue + # + # Wonderfully consistent library, eh? + # 160013 - svn:// and file:// + # 175002 - http(s):// + # 175007 - http(s):// (this repo required authorization, too...) + # More codes may be discovered later... + if ($errno == 175007 || $errno == 175002 || $errno == 160013) { + return; + } + die "Error from SVN, ($errno): ", $err->expanded_message,"\n"; +} + +# svn_log_changed_path_t objects passed to get_log are likely to be +# overwritten even if only the refs are copied to an external variable, +# so we should dup the structures in their entirety. Using an externally +# passed pool (instead of our temporary and quickly cleared pool in +# Git::SVN::Ra) does not help matters at all... +sub dup_changed_paths { + my ($paths) = @_; + return undef unless $paths; + my %ret; + foreach my $p (keys %$paths) { + my $i = $paths->{$p}; + my %s = map { $_ => $i->$_ } + qw/copyfrom_path copyfrom_rev action/; + $ret{$p} = \%s; + } + \%ret; +} + package Git::SVN::Log; use strict; use warnings; From 2fa6a23efb200cea814add88ce1d1d193ba83860 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 28 Jan 2007 04:02:01 -0800 Subject: [PATCH 104/201] git-svn: correctly track diff-less copies with do_switch Also, this should allow for the tracking of new, but empty directories where we would want to see the log message. Signed-off-by: Eric Wong --- git-svn.perl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/git-svn.perl b/git-svn.perl index 5d398ee65f..36e5c57adf 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1167,6 +1167,7 @@ sub find_parent_branch { 1, $ed) or die "SVN connection failed somewhere...\n"; } + $ed->{new_fetch} = 1; return $self->make_log_entry($rev, [$parent], $ed); } not_found: @@ -1202,6 +1203,7 @@ sub do_fetch { return $log_entry; } $ed = SVN::Git::Fetcher->new($self); + $ed->{new_fetch} = 1; } unless ($self->ra->gs_do_update($last_rev, $rev, $self->{path}, 1, $ed)) { @@ -1275,7 +1277,7 @@ sub make_log_entry { my ($self, $rev, $parents, $ed) = @_; my $untracked = $self->get_untracked($ed); - return undef if ($ed->{nr} == 0 && scalar @$untracked == 0); + return undef if (! $ed->{new_fetch} && ! $ed->{nr} && ! @$untracked); open my $un, '>>', "$self->{dir}/unhandled.log" or croak $!; print $un "r$rev\n" or croak $!; From 2b27f6c8847ebee631e7ad17ac9986e461d7674b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 28 Jan 2007 04:59:05 -0800 Subject: [PATCH 105/201] git-svn: correctly handle do_{switch,update} in deep directories The do_update or do_switch functions in SVN only allow for a single path component; so 'path/to/deep/dir' would be interpreted as 'path'. SVN 1.4.x has a reparent function that can let us change the session to use a higher-level root of the repository, so we can use that for do_switch (which still doesn't seem to work in SVN 1.4.3 (a fix was attempted, but they missed the rest of the typemap changes needed in trunk...)). On the do_update side, we can use set_path on higher level directories and set them to a newer revision so they don't get updated. We can't do this with do_switch, either, because the relative path we're tracking can change (directory moving into a child of itself). Because of these changes, we need to double check that our Fetch editor is correctly performing stripping on any prefixed paths from update, otherwise we'll just die() because that would be a bug. Added a test case which helped me notice and fix problems with do_switch, too. Signed-off-by: Eric Wong --- git-svn.perl | 46 ++++++++++++++++++++++++++------ t/t9104-git-svn-follow-parent.sh | 31 +++++++++++++++++++++ 2 files changed, 69 insertions(+), 8 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 36e5c57adf..dcc7fd4603 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1153,7 +1153,7 @@ sub find_parent_branch { if ($self->ra->can_do_switch) { print STDERR "Following parent with do_switch\n"; # do_switch works with svn/trunk >= r22312, but that - # is not included with SVN 1.4.2 (the latest version + # is not included with SVN 1.4.3 (the latest version # at the moment), so we can't rely on it $self->{last_commit} = $parent; $ed = SVN::Git::Fetcher->new($self); @@ -1621,7 +1621,10 @@ sub open_directory { sub git_path { my ($self, $path) = @_; - $path =~ s!$self->{path_strip}!! if $self->{path_strip}; + if ($self->{path_strip}) { + $path =~ s!$self->{path_strip}!! or + die "Failed to strip path '$path' ($self->{path_strip})\n"; + } $path; } @@ -2249,25 +2252,52 @@ sub gs_do_update { my ($self, $rev_a, $rev_b, $path, $recurse, $editor) = @_; my $pool = SVN::Pool->new; $editor->set_path_strip($path); - my $reporter = $self->do_update($rev_b, $path, $recurse, - $editor, $pool); + my (@pc) = split m#/#, $path; + my $reporter = $self->do_update($rev_b, (@pc ? shift @pc : ''), + $recurse, $editor, $pool); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); + + # Since we can't rely on svn_ra_reparent being available, we'll + # just have to do some magic with set_path to make it so + # we only want a partial path. + my $sp = ''; + my $final = join('/', @pc); + while (@pc) { + $reporter->set_path($sp, $rev_b, 0, @lock, $pool); + $sp .= '/' if length $sp; + $sp .= shift @pc; + } + die "BUG: '$sp' != '$final'\n" if ($sp ne $final); + my $new = ($rev_a == $rev_b); - $reporter->set_path('', $rev_a, $new, @lock, $pool); + $reporter->set_path($sp, $rev_a, $new, @lock, $pool); + $reporter->finish_report($pool); $pool->clear; $editor->{git_commit_ok}; } +# this requires SVN 1.4.3 or later (do_switch didn't work before 1.4.3, and +# svn_ra_reparent didn't work before 1.4) sub gs_do_switch { my ($self, $rev_a, $rev_b, $path, $recurse, $url_b, $editor) = @_; my $pool = SVN::Pool->new; - $editor->set_path_strip($path); - my $reporter = $self->do_switch($rev_b, $path, $recurse, - $url_b, $editor, $pool); + + my $full_url = $self->{url}; + my $old_url = $full_url; + $full_url .= "/$path" if length $path; + SVN::_Ra::svn_ra_reparent($self->{session}, $full_url, $pool); + $self->{url} = $full_url; + + my $reporter = $self->do_switch($rev_b, '', + $recurse, $url_b, $editor, $pool); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); $reporter->set_path('', $rev_a, 0, @lock, $pool); $reporter->finish_report($pool); + + SVN::_Ra::svn_ra_reparent($self->{session}, $old_url, $pool); + $self->{url} = $old_url; + $pool->clear; $editor->{git_commit_ok}; } diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index bfb718886f..6d243f8488 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -101,6 +101,37 @@ test_expect_success 'follow deleted directory' " test -z \"\`git ls-tree -z refs/remotes/glob\`\" " +# ref: r9270 of the Subversion repository: (http://svn.collab.net/repos/svn) +# in trunk/subversion/bindings/swig/perl +test_expect_success '' " + mkdir -p import/trunk/subversion/bindings/swig/perl/t && + for i in a b c ; do \ + echo \$i > import/trunk/subversion/bindings/swig/perl/\$i.pm && + echo _\$i > import/trunk/subversion/bindings/swig/perl/t/\$i.t; \ + done && + echo 'bad delete test' > \ + import/trunk/subversion/bindings/swig/perl/t/larger-parent && + echo 'bad delete test 2' > \ + import/trunk/subversion/bindings/swig/perl/another-larger && + cd import && + svn import -m 'r9270 test' . $svnrepo/r9270 && + cd .. && + svn co $svnrepo/r9270/trunk/subversion/bindings/swig/perl r9270 && + cd r9270 && + svn mkdir native && + svn mv t native/t && + for i in a b c; do svn mv \$i.pm native/\$i.pm; done && + echo z >> native/t/c.t && + svn commit -m 'reorg test' && + cd .. && + git-svn init -i r9270-t \ + $svnrepo/r9270/trunk/subversion/bindings/swig/perl/native/t && + git-svn fetch -i r9270-t --follow-parent && + test \`git rev-list r9270-t | wc -l\` -eq 2 && + test \"\`git ls-tree --name-only r9270-t~1\`\" = \ + \"\`git ls-tree --name-only r9270-t\`\" + " + test_debug 'gitk --all &' test_done From ce2a0f2f9d9fd8eee8eaa1f24a60bdafade80c24 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 28 Jan 2007 21:34:23 -0800 Subject: [PATCH 106/201] git-svn: stop using path names as refnames with --follow-parent Using path names as refnames breaks horribly if a user is tracking one large, toplevel directory, and a lower-level directory is followed from another project is a parent of another ref, as it will cause refnames such as: 'refs/remotes/trunk/path/to/stuff', which will conflict with a refname of 'refs/remotes/trunk'. Now we just append @$revno to the end of it the current refname. And if we have followed back to a grandparent, then we'll strip any existing '@$parent_revno' strings before appending our own '@$revno' string to it. Signed-off-by: Eric Wong --- git-svn.perl | 6 ++++-- t/t9104-git-svn-follow-parent.sh | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index dcc7fd4603..4f7938ab7e 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1135,10 +1135,12 @@ sub find_parent_branch { last if $gs; } unless ($gs) { - my $ref_id = $branch_from; - $ref_id .= "\@$r" if find_ref($ref_id); + my $ref_id = $self->{ref_id}; + $ref_id =~ s/\@\d+$//; + $ref_id .= "\@$r"; # just grow a tail if we're not unique enough :x $ref_id .= '-' while find_ref($ref_id); + print STDERR "Initializing parent: $ref_id\n"; $gs = Git::SVN->init($new_url, '', $ref_id, $ref_id); } my ($r0, $parent) = $gs->find_rev_before($r, 1); diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 6d243f8488..0f4e736271 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -30,10 +30,12 @@ test_expect_success 'initialize repo' " test_expect_success 'init and fetch --follow-parent a moved directory' " git-svn init -i thunk $svnrepo/thunk && git-svn fetch --follow-parent -i thunk && - test \"\`git-rev-parse --verify refs/remotes/trunk\`\" \ + test \"\`git-rev-parse --verify refs/remotes/thunk@2\`\" \ = \"\`git-rev-parse --verify refs/remotes/thunk~1\`\" && test \"\`git-cat-file blob refs/remotes/thunk:readme |\ - sed -n -e '3p'\`\" = goodbye + sed -n -e '3p'\`\" = goodbye && + test -n \"\`git-config --get svn-remote.git-svn.fetch \ + '^trunk:refs/remotes/thunk@2$'\`\" " test_expect_success 'init and fetch from one svn-remote' " From 24e22aa8a5762b11a8025c6ad82c945828667d0f Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 29 Jan 2007 00:07:49 -0800 Subject: [PATCH 107/201] git-svn: cleanup: move editor-specific variables into the editor namespace Also removed some unused/redundant functions. Signed-off-by: Eric Wong --- git-svn.perl | 37 ++++++++++++------------------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 4f7938ab7e..20c6fc768b 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -8,8 +8,8 @@ $GIT_SVN_INDEX $GIT_SVN $GIT_DIR $GIT_SVN_DIR $REVDB $_follow_parent $sha1 $sha1_short $_revision - $_cp_remote $_upgrade $_rmdir $_q $_cp_similarity - $_find_copies_harder $_l $_authors %users/; + $_cp_remote $_upgrade $_q + $_authors %users/; $AUTHOR = 'Eric Wong '; $VERSION = '@@GIT_VERSION@@'; @@ -83,10 +83,10 @@ BEGIN 'branches|b=s' => \$_branches ); my %init_opts = ( 'template=s' => \$_template, 'shared' => \$_shared ); my %cmt_opts = ( 'edit|e' => \$_edit, - 'rmdir' => \$_rmdir, - 'find-copies-harder' => \$_find_copies_harder, - 'l=i' => \$_l, - 'copy-similarity|C=i'=> \$_cp_similarity + 'rmdir' => \$SVN::Git::Editor::_rmdir, + 'find-copies-harder' => \$SVN::Git::Editor::_find_copies_harder, + 'l=i' => \$SVN::Git::Editor::_rename_limit, + 'copy-similarity|C=i'=> \$SVN::Git::Editor::_cp_similarity ); my %cmd = ( @@ -1559,19 +1559,6 @@ sub _read_password { package main; -sub uri_encode { - my ($f) = @_; - $f =~ s#([^a-zA-Z0-9\*!\:_\./\-])#uc sprintf("%%%02x",ord($1))#eg; - $f -} - -sub uri_decode { - my ($f) = @_; - $f =~ tr/+/ /; - $f =~ s/%([A-F0-9]{2})/chr hex($1)/ge; - $f -} - { my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file. $SVN::Node::dir.$SVN::Node::unknown. @@ -1806,7 +1793,7 @@ sub close_edit { } package SVN::Git::Editor; -use vars qw/@ISA/; +use vars qw/@ISA $_rmdir $_cp_similarity $_find_copies_harder $_rename_limit/; use strict; use warnings; use Carp qw/croak/; @@ -1846,13 +1833,13 @@ sub new { sub generate_diff { my ($tree_a, $tree_b) = @_; my @diff_tree = qw(diff-tree -z -r); - if ($::_cp_similarity) { - push @diff_tree, "-C$::_cp_similarity"; + if ($_cp_similarity) { + push @diff_tree, "-C$_cp_similarity"; } else { push @diff_tree, '-C'; } - push @diff_tree, '--find-copies-harder' if $::_find_copies_harder; - push @diff_tree, "-l$::_l" if defined $::_l; + push @diff_tree, '--find-copies-harder' if $_find_copies_harder; + push @diff_tree, "-l$_rename_limit" if defined $_rename_limit; push @diff_tree, $tree_a, $tree_b; my ($diff_fh, $ctx) = command_output_pipe(@diff_tree); local $/ = "\0"; @@ -2163,7 +2150,7 @@ sub apply_diff { fatal("Invalid change type: $f\n"); } } - $self->rmdirs if $::_rmdir; + $self->rmdirs if $_rmdir; if (@$mods == 0) { $self->abort_edit; } else { From 90c1b15da303fa40457e0a9e5c11a57adbfa1879 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 29 Jan 2007 16:27:08 -0800 Subject: [PATCH 108/201] git-svn: just use Digest::MD5 instead of requiring it Historically, git-svn did not always use Digest::MD5 because it did not use the SVN::Delta::Editor interfaces. Nowadays it does, and the requires make strace more noisy. Signed-off-by: Eric Wong --- git-svn.perl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 20c6fc768b..e387504b88 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1577,6 +1577,7 @@ package SVN::Git::Fetcher; use warnings; use Carp qw/croak/; use IO::File qw//; +use Digest::MD5; # file baton members: path, mode_a, mode_b, pool, fh, blob, base sub new { @@ -1590,7 +1591,6 @@ sub new { $self->{absent_dir} = {}; $self->{absent_file} = {}; $self->{gii} = $git_svn->tmp_index_do(sub { Git::IndexInfo->new }); - require Digest::MD5; $self; } @@ -1798,6 +1798,7 @@ package SVN::Git::Editor; use warnings; use Carp qw/croak/; use IO::File; +use Digest::MD5; sub new { my ($class, $opts) = @_; @@ -1826,7 +1827,6 @@ sub new { $self->{rm} = { }; $self->{path_prefix} = length $self->{svn_path} ? "$self->{svn_path}/" : ''; - require Digest::MD5; return $self; } From f7c3fc4a269ad4f12cc9d97d70e46ba3d5dfdb79 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 29 Jan 2007 18:34:55 -0800 Subject: [PATCH 109/201] git-svn: reinstate the default SVN error handler after using get_log We don't need our own error handler for other operations. Also add a message about the successfully do_switch or do_update in follow-parent for debugging do_switch failures with svn:// and svn+ssh:// connections. Signed-off-by: Eric Wong --- git-svn.perl | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index e387504b88..bbdaeea98a 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1169,6 +1169,7 @@ sub find_parent_branch { 1, $ed) or die "SVN connection failed somewhere...\n"; } + print STDERR "Successfully followed parent\n"; $ed->{new_fetch} = 1; return $self->make_log_entry($rev, [$parent], $ed); } @@ -2295,9 +2296,6 @@ sub gs_fetch_loop_common { my ($self, $base, $head, @gs) = @_; my $inc = 1000; my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc); - my $err_handler = $SVN::Error::handler; - my $err; - $SVN::Error::handler = sub { ($err) = @_; skip_unknown_revs($err); }; my @paths = @gs == 1 ? ($gs[0]->{path}) : (''); foreach my $gs (@gs) { if (my $last_commit = $gs->last_commit) { @@ -2307,8 +2305,16 @@ sub gs_fetch_loop_common { } while (1) { my @revs; + my $err; + my $err_handler = $SVN::Error::handler; + $SVN::Error::handler = sub { + ($err) = @_; + skip_unknown_revs($err); + }; $self->get_log(\@paths, $min, $max, 0, 1, 1, sub { push @revs, [ dup_changed_paths($_[0]), $_[1] ]; }); + $SVN::Error::handler = $err_handler; + if (! @revs && $err && $max >= $head) { print STDERR "Branch probably deleted:\n ", $err->expanded_message, @@ -2335,7 +2341,6 @@ sub gs_fetch_loop_common { $max += $inc; $max = $head if ($max > $head); } - $SVN::Error::handler = $err_handler; } sub minimize_url { From 5d3b7cd5fe5d0410915cc68b641415901e1b113e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 29 Jan 2007 19:16:01 -0800 Subject: [PATCH 110/201] git-svn: don't rely on do_switch + reparenting with svn(+ssh):// I can't seem to figure out what I or the SVN libraries are doing wrong, but it appears to be related to reparent and probably some global structure that gets reset if multiple SVN connections are being used. So now, in order to use do_switch; we'll open a new connection to the repository with the complete URL; but we can't seem to ever use an existing Ra object after another one has been created... Signed-off-by: Eric Wong --- git-svn.perl | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index bbdaeea98a..4e357dfcef 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -870,7 +870,7 @@ sub new { sub ra { my ($self) = shift; - $self->{ra} ||= Git::SVN::Ra->new($self->{url}); + Git::SVN::Ra->new($self->{url}); } sub rel_path { @@ -897,9 +897,10 @@ sub copy_remote_ref { sub traverse_ignore { my ($self, $fh, $path, $r) = @_; $path =~ s#^/+##g; - my ($dirent, undef, $props) = $self->ra->get_dir($path, $r); + my $ra = $self->ra; + my ($dirent, undef, $props) = $ra->get_dir($path, $r); my $p = $path; - $p =~ s#^\Q$self->{ra}->{svn_path}\E/##; + $p =~ s#^\Q$ra->{svn_path}\E/##; print $fh length $p ? "\n# $p\n" : "\n# /\n"; if (my $s = $props->{'svn:ignore'}) { $s =~ s/[\r\n]+/\n/g; @@ -1027,7 +1028,7 @@ sub get_commit_parents { sub full_url { my ($self) = @_; - $self->ra->{url} . (length $self->{path} ? '/' . $self->{path} : ''); + $self->{url} . (length $self->{path} ? '/' . $self->{path} : ''); } sub do_git_commit { @@ -2165,7 +2166,7 @@ package Git::SVN::Ra; use strict; use warnings; my ($can_do_switch); -my %RA; +my $RA; BEGIN { # enforce temporary pool usage for some simple functions @@ -2185,7 +2186,7 @@ BEGIN sub new { my ($class, $url) = @_; $url =~ s!/+$!!; - return $RA{$url} if $RA{$url}; + return $RA if ($RA && $RA->{url} eq $url); SVN::_Core::svn_config_ensure($config_dir, undef); my ($baton, $callbacks) = SVN::Core::auth_open_helper([ @@ -2211,11 +2212,11 @@ sub new { $self->{svn_path} = $url; $self->{repos_root} = $self->get_repos_root; $self->{svn_path} =~ s#^\Q$self->{repos_root}\E/*##; - $RA{$url} = bless $self, $class; + $RA = bless $self, $class; } sub DESTROY { - # do not call the real DESTROY since we store ourselves in %RA + # do not call the real DESTROY since we store ourselves in $RA } sub get_log { @@ -2276,17 +2277,28 @@ sub gs_do_switch { my $full_url = $self->{url}; my $old_url = $full_url; $full_url .= "/$path" if length $path; - SVN::_Ra::svn_ra_reparent($self->{session}, $full_url, $pool); - $self->{url} = $full_url; - - my $reporter = $self->do_switch($rev_b, '', - $recurse, $url_b, $editor, $pool); + my ($ra, $reparented); + if ($old_url ne $full_url) { + if ($old_url !~ m#^svn(\+ssh)?://#) { + SVN::_Ra::svn_ra_reparent($self->{session}, $full_url, + $pool); + $self->{url} = $full_url; + $reparented = 1; + } else { + $ra = Git::SVN::Ra->new($full_url); + } + } + $ra ||= $self; + my $reporter = $ra->do_switch($rev_b, '', + $recurse, $url_b, $editor, $pool); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); $reporter->set_path('', $rev_a, 0, @lock, $pool); $reporter->finish_report($pool); - SVN::_Ra::svn_ra_reparent($self->{session}, $old_url, $pool); - $self->{url} = $old_url; + if ($reparented) { + SVN::_Ra::svn_ra_reparent($self->{session}, $old_url, $pool); + $self->{url} = $old_url; + } $pool->clear; $editor->{git_commit_ok}; From 289370578ca5833641fbb59813173ac6db1986d1 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 30 Jan 2007 00:35:18 -0800 Subject: [PATCH 111/201] git-svn: fetch tracks initial change with --follow-parent We were still skipping path information from get_log if we are tracking /r9270/drunk/subversion/bindings/..., but got something like this in the log: A /r9270/drunk (from /r9270/trunk:14) Signed-off-by: Eric Wong --- git-svn.perl | 17 ++++++++++++++--- t/t9104-git-svn-follow-parent.sh | 14 +++++++++++++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 4e357dfcef..b0248c9487 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1090,6 +1090,19 @@ sub revisions_eq { return 1; } +sub match_paths { + my ($self, $paths) = @_; + return 1 if $paths->{'/'}; + $self->{path_regex} ||= qr/^\/\Q$self->{path}\E\/?/; + grep /$self->{path_regex}/, keys %$paths and return 1; + my $c = ''; + foreach (split m#/#, $self->rel_path) { + $c .= "/$_"; + return 1 if $paths->{$c}; + } + return 0; +} + sub find_parent_branch { my ($self, $paths, $rev) = @_; return undef unless $::_follow_parent; @@ -2313,7 +2326,6 @@ sub gs_fetch_loop_common { if (my $last_commit = $gs->last_commit) { $gs->assert_index_clean($last_commit); } - $gs->{path_regex} = qr/^\/\Q$gs->{path}\E\/?/; } while (1) { my @revs; @@ -2339,8 +2351,7 @@ sub gs_fetch_loop_common { my ($paths, $r) = @$_; foreach my $gs (@gs) { if ($paths) { - grep /$gs->{path_regex}/, keys %$paths - or next; + $gs->match_paths($paths) or next; } next if defined $gs->rev_db_get($r); if (my $log_entry = $gs->do_fetch($paths, $r)) { diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 0f4e736271..dcec16bda2 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -105,7 +105,7 @@ test_expect_success 'follow deleted directory' " # ref: r9270 of the Subversion repository: (http://svn.collab.net/repos/svn) # in trunk/subversion/bindings/swig/perl -test_expect_success '' " +test_expect_success 'follow-parent avoids deleting relevant info' " mkdir -p import/trunk/subversion/bindings/swig/perl/t && for i in a b c ; do \ echo \$i > import/trunk/subversion/bindings/swig/perl/\$i.pm && @@ -134,6 +134,18 @@ test_expect_success '' " \"\`git ls-tree --name-only r9270-t\`\" " +test_expect_success "track initial change if it was only made to parent" " + svn cp -m 'wheee!' $svnrepo/r9270/trunk $svnrepo/r9270/drunk && + git-svn init -i r9270-d \ + $svnrepo/r9270/drunk/subversion/bindings/swig/perl/native/t && + git-svn fetch -i r9270-d --follow-parent && + test \`git rev-list r9270-d | wc -l\` -eq 3 && + test \"\`git ls-tree --name-only r9270-t\`\" = \ + \"\`git ls-tree --name-only r9270-d\`\" && + test \"\`git rev-parse r9270-t\`\" = \ + \"\`git rev-parse r9270-d~1\`\" + " + test_debug 'gitk --all &' test_done From f0ecca1041aff2e1398edcdaf5f0ce08a96f5f0f Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 30 Jan 2007 13:11:14 -0800 Subject: [PATCH 112/201] git-svn: remove the 'rebuild' command and make the functionality automatic Since refs/remotes/* are not automatically cloned, we expect the user to be capable of copying those references themselves anyways. Also removed the documentation for --ignore-nodate while we're at it; it has also been made automatic. Signed-off-by: Eric Wong --- Documentation/git-svn.txt | 41 ++--------------- git-svn.perl | 92 +++++++++++++++------------------------ 2 files changed, 39 insertions(+), 94 deletions(-) diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 6daba241e9..22dd7b219d 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -93,16 +93,6 @@ remotes/git-svn. commit. All merging is assumed to have taken place independently of git-svn functions. -'rebuild':: - Not a part of daily usage, but this is a useful command if - you've just cloned a repository (using gitlink:git-clone[1]) that was - tracked with git-svn. Unfortunately, git-clone does not clone - git-svn metadata and the svn working tree that git-svn uses for - its operations. This rebuilds the metadata so git-svn can - resume fetch operations. A Subversion URL may be optionally - specified at the command-line if the directory/repository you're - tracking has moved or changed protocols. - 'show-ignore':: Recursively finds and lists the svn:ignore property on directories. The output is suitable for appending to @@ -322,9 +312,9 @@ config key: svn.followparent --no-metadata:: This gets rid of the git-svn-id: lines at the end of every commit. - With this, you lose the ability to use the rebuild command. If - you ever lose your .git/svn/git-svn/.rev_db file, you won't be - able to fetch again, either. This is fine for one-shot imports. + If you lose your .git/svn/git-svn/.rev_db file, git-svn will not + be able to rebuild it and you won't be able to fetch again, + either. This is fine for one-shot imports. The 'git-svn log' command will not work on repositories using this, either. @@ -333,31 +323,6 @@ config key: svn.nometadata -- -COMPATIBILITY OPTIONS ---------------------- --- - ---upgrade:: -Only used with the 'rebuild' command. - -Run this if you used an old version of git-svn that used -"git-svn-HEAD" instead of "remotes/git-svn" as the branch -for tracking the remote. - ---ignore-nodate:: -Only used with the 'fetch' command. - -By default git-svn will crash if it tries to import a revision -from SVN which has '(no date)' listed as the date of the revision. -This is repository corruption on SVN's part, plain and simple. -But sometimes you really need those revisions anyway. - -If supplied git-svn will convert '(no date)' entries to the UNIX -epoch (midnight on Jan. 1, 1970). Yes, that's probably very wrong. -SVN was very wrong. - --- - Basic Examples ~~~~~~~~~~~~~~ diff --git a/git-svn.perl b/git-svn.perl index b0248c9487..845f22af59 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -106,9 +106,6 @@ BEGIN { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ], 'show-ignore' => [ \&cmd_show_ignore, "Show svn:ignore listings", { 'revision|r=i' => \$_revision } ], - rebuild => [ \&cmd_rebuild, "Rebuild git-svn metadata (after git clone)", - { 'copy-remote|remote=s' => \$_cp_remote, - 'upgrade' => \$_upgrade } ], 'multi-init' => [ \&cmd_multi_init, 'Initialize multiple trees (like git-svnimport)', { %multi_opts, %init_opts, %remote_opts, @@ -166,7 +163,7 @@ BEGIN version() if $_version; usage(1) unless defined $cmd; load_authors() if $_authors; -unless ($cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/) { +unless ($cmd =~ /^(?:init|multi-init|commit-diff)$/) { Git::SVN::Migration::migration_check(); } eval { @@ -211,47 +208,6 @@ sub version { exit 0; } -sub cmd_rebuild { - my $url = shift; - my $gs = $url ? Git::SVN->init($url) - : eval { Git::SVN->new }; - $gs ||= Git::SVN->_new; - if (!verify_ref($gs->refname.'^0')) { - $gs->copy_remote_ref; - } - - my ($rev_list, $ctx) = command_output_pipe("rev-list", $gs->refname); - my $latest; - my $svn_uuid; - while (<$rev_list>) { - chomp; - my $c = $_; - fatal "Non-SHA1: $c\n" unless $c =~ /^$sha1$/o; - my ($url, $rev, $uuid) = cmt_metadata($c); - - # ignore merges (from set-tree) - next if (!defined $rev || !$uuid); - - # if we merged or otherwise started elsewhere, this is - # how we break out of it - if ((defined $svn_uuid && ($uuid ne $svn_uuid)) || - ($gs->{url} && $url && ($url ne $gs->{url}))) { - next; - } - - unless (defined $latest) { - if (!$gs->{url} && !$url) { - fatal "SVN repository location required\n"; - } - $gs = Git::SVN->init($url); - $latest = $rev; - } - $gs->rev_db_set($rev, $c); - print "r$rev = $c\n"; - } - command_close_pipe($rev_list, $ctx); -} - sub do_git_init_db { unless (-d $ENV{GIT_DIR}) { my @init_db = ('init'); @@ -863,6 +819,9 @@ sub new { $self->{url} = command_oneline('config', '--get', "svn-remote.$repo_id.url") or die "Failed to read \"svn-remote.$repo_id.url\" in config\n"; + if (-z $self->{db_path} && ::verify_ref($self->refname.'^0')) { + $self->rebuild; + } $self; } @@ -883,17 +842,6 @@ sub rel_path { $url; } -sub copy_remote_ref { - my ($self) = @_; - my $origin = $::_cp_remote ? $::_cp_remote : 'origin'; - my $ref = $self->refname; - if (command('ls-remote', $origin, $ref)) { - command_noisy('fetch', $origin, "$ref:$ref"); - } elsif ($::_cp_remote && !$::_upgrade) { - die "Unable to find remote reference: $ref on $origin\n"; - } -} - sub traverse_ignore { my ($self, $fh, $path, $r) = @_; $path =~ s#^/+##g; @@ -1359,6 +1307,38 @@ sub set_tree { } } +sub rebuild { + my ($self) = @_; + print "Rebuilding $self->{db_path} ...\n"; + my ($rev_list, $ctx) = command_output_pipe("rev-list", $self->refname); + my $latest; + my $full_url = $self->full_url; + my $svn_uuid; + while (<$rev_list>) { + chomp; + my $c = $_; + die "Non-SHA1: $c\n" unless $c =~ /^$::sha1$/o; + my ($url, $rev, $uuid) = ::cmt_metadata($c); + + # ignore merges (from set-tree) + next if (!defined $rev || !$uuid); + + # if we merged or otherwise started elsewhere, this is + # how we break out of it + if ((defined $svn_uuid && ($uuid ne $svn_uuid)) || + ($full_url && $url && ($url ne $full_url))) { + next; + } + $latest ||= $rev; + $svn_uuid ||= $uuid; + + $self->rev_db_set($rev, $c); + print "r$rev = $c\n"; + } + command_close_pipe($rev_list, $ctx); + print "Done rebuilding $self->{db_path}\n"; +} + # rev_db: # Tie::File seems to be prone to offset errors if revisions get sparse, # it's not that fast, either. Tie::File is also not in Perl 5.6. So From 8a603774def11e5a90ae2dbbc0c8d4abacdb2d99 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 31 Jan 2007 02:45:50 -0800 Subject: [PATCH 113/201] git-svn: fix several fetch bugs related to repeated invocations We no longer delete the top-level directory even if it got deleted from the upstream repository. In gs_do_update; we double-check that the path we're tracking exists at both endpoints before proceeding. We have also added additional protection against fetching revisions out-of-order. To simplify our internal interfaces, I've disabled passing the 'recursive' flag to the gs_do_{switch,update} wrapper functions since we always want it in git-svn. We also pass the entire Git::SVN object rather than just the path because it helped me debug. When printing progress, the refname is printed out to make it less confusing when multi-fetch is running. Signed-off-by: Eric Wong --- git-svn.perl | 42 +++++++++++++++++++++++--------- t/t9104-git-svn-follow-parent.sh | 16 +++++++++--- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 845f22af59..2afa2537b9 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -981,6 +981,12 @@ sub full_url { sub do_git_commit { my ($self, $log_entry) = @_; + my $lr = $self->last_rev; + if (defined $lr && $lr >= $log_entry->{revision}) { + die "Last fetched revision of ", $self->refname, + " was r$lr, but we are about to fetch: ", + "r$log_entry->{revision}!\n"; + } if (my $c = $self->rev_db_get($log_entry->{revision})) { croak "$log_entry->{revision} = $c already exists! ", "Why are we refetching it?\n"; @@ -1024,7 +1030,7 @@ sub do_git_commit { $self->{last_rev} = $log_entry->{revision}; $self->{last_commit} = $commit; - print "r$log_entry->{revision} = $commit\n"; + print "r$log_entry->{revision} = $commit ($self->{ref_id})\n"; return $commit; } @@ -1121,14 +1127,13 @@ sub find_parent_branch { # at the moment), so we can't rely on it $self->{last_commit} = $parent; $ed = SVN::Git::Fetcher->new($self); - $gs->ra->gs_do_switch($r0, $rev, $gs->{path}, 1, + $gs->ra->gs_do_switch($r0, $rev, $gs, $self->full_url, $ed) or die "SVN connection failed somewhere...\n"; } else { print STDERR "Following parent with do_update\n"; $ed = SVN::Git::Fetcher->new($self); - $self->ra->gs_do_update($rev, $rev, $self->{path}, - 1, $ed) + $self->ra->gs_do_update($rev, $rev, $self, $ed) or die "SVN connection failed somewhere...\n"; } print STDERR "Successfully followed parent\n"; @@ -1170,8 +1175,7 @@ sub do_fetch { $ed = SVN::Git::Fetcher->new($self); $ed->{new_fetch} = 1; } - unless ($self->ra->gs_do_update($last_rev, $rev, - $self->{path}, 1, $ed)) { + unless ($self->ra->gs_do_update($last_rev, $rev, $self, $ed)) { die "SVN connection failed somewhere...\n"; } $self->make_log_entry($rev, \@parents, $ed); @@ -1616,6 +1620,8 @@ sub delete_entry { my ($self, $path, $rev, $pb) = @_; my $gpath = $self->git_path($path); + return undef if ($gpath eq ''); + # remove entire directories. if (command('ls-tree', $self->{c}, '--', $gpath) =~ /^040000 tree/) { my ($ls, $ctx) = command_output_pipe(qw/ls-tree @@ -2233,12 +2239,23 @@ sub uuid { } sub gs_do_update { - my ($self, $rev_a, $rev_b, $path, $recurse, $editor) = @_; + my ($self, $rev_a, $rev_b, $gs, $editor) = @_; + my $new = ($rev_a == $rev_b); + my $path = $gs->{path}; + + my $ta = $self->check_path($path, $rev_a); + my $tb = $new ? $ta : $self->check_path($path, $rev_b); + return 1 if ($tb != $SVN::Node::dir && $ta != $SVN::Node::dir); + if ($ta == $SVN::Node::none) { + $rev_a = $rev_b; + $new = 1; + } + my $pool = SVN::Pool->new; $editor->set_path_strip($path); my (@pc) = split m#/#, $path; my $reporter = $self->do_update($rev_b, (@pc ? shift @pc : ''), - $recurse, $editor, $pool); + 1, $editor, $pool); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); # Since we can't rely on svn_ra_reparent being available, we'll @@ -2253,7 +2270,6 @@ sub gs_do_update { } die "BUG: '$sp' != '$final'\n" if ($sp ne $final); - my $new = ($rev_a == $rev_b); $reporter->set_path($sp, $rev_a, $new, @lock, $pool); $reporter->finish_report($pool); @@ -2264,7 +2280,8 @@ sub gs_do_update { # this requires SVN 1.4.3 or later (do_switch didn't work before 1.4.3, and # svn_ra_reparent didn't work before 1.4) sub gs_do_switch { - my ($self, $rev_a, $rev_b, $path, $recurse, $url_b, $editor) = @_; + my ($self, $rev_a, $rev_b, $gs, $url_b, $editor) = @_; + my $path = $gs->{path}; my $pool = SVN::Pool->new; my $full_url = $self->{url}; @@ -2282,8 +2299,7 @@ sub gs_do_switch { } } $ra ||= $self; - my $reporter = $ra->do_switch($rev_b, '', - $recurse, $url_b, $editor, $pool); + my $reporter = $ra->do_switch($rev_b, '', 1, $url_b, $editor, $pool); my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); $reporter->set_path('', $rev_a, 0, @lock, $pool); $reporter->finish_report($pool); @@ -2333,6 +2349,8 @@ sub gs_fetch_loop_common { if ($paths) { $gs->match_paths($paths) or next; } + my $lr = $gs->last_rev; + next if defined $lr && $lr >= $r; next if defined $gs->rev_db_get($r); if (my $log_entry = $gs->do_fetch($paths, $r)) { $gs->do_git_commit($log_entry); diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index dcec16bda2..41b9c19d45 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -95,12 +95,12 @@ test_expect_success 'follow higher-level parent' " " test_expect_success 'follow deleted directory' " - svn mv -m 'bye!' $svnrepo/glob/blob/hi $svnrepo/glob/blob/bye&& + svn mv -m 'bye!' $svnrepo/glob/blob/hi $svnrepo/glob/blob/bye && svn rm -m 'remove glob' $svnrepo/glob && git-svn init -i glob $svnrepo/glob && git-svn fetch -i glob && - test \"\`git cat-file blob refs/remotes/glob~1:blob/bye\`\" = hi && - test -z \"\`git ls-tree -z refs/remotes/glob\`\" + test \"\`git cat-file blob refs/remotes/glob:blob/bye\`\" = hi && + test \"\`git ls-tree refs/remotes/glob | wc -l \`\" -eq 1 " # ref: r9270 of the Subversion repository: (http://svn.collab.net/repos/svn) @@ -146,6 +146,16 @@ test_expect_success "track initial change if it was only made to parent" " \"\`git rev-parse r9270-d~1\`\" " +test_expect_success "multi-fetch continues to work" " + git-svn multi-fetch --follow-parent + " + +test_expect_success "multi-fetch works off a 'clean' repository" " + rm -r $GIT_DIR/svn $GIT_DIR/refs/remotes $GIT_DIR/logs && + mkdir $GIT_DIR/svn && + git-svn multi-fetch --follow-parent + " + test_debug 'gitk --all &' test_done From 9760adccccc0cc4dccc2f28765611550db640ceb Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 31 Jan 2007 03:06:56 -0800 Subject: [PATCH 114/201] git-svn: reinstate --no-metadata, add --svn-remote=, variable cleanups --svn-remote allows the default remote name to be overridden (useful for tracking multiple SVN repositories). Signed-off-by: Eric Wong --- Documentation/git-svn.txt | 6 ++++++ git-svn.perl | 42 +++++++++++++++++---------------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 22dd7b219d..2914042587 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -301,6 +301,12 @@ section on '<>' for more information on using GIT_SVN_ID. +-R:: +--svn-remote :: + Specify the [svn-remote ""] section to use, + this allows multiple repositories to be tracked. + Default: git-svn + --follow-parent:: This is especially helpful when we're tracking a directory that has been moved around within the repository, or if we diff --git a/git-svn.perl b/git-svn.perl index 2afa2537b9..cc5736d793 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -4,12 +4,8 @@ use warnings; use strict; use vars qw/ $AUTHOR $VERSION - $SVN_URL - $GIT_SVN_INDEX $GIT_SVN - $GIT_DIR $GIT_SVN_DIR $REVDB - $_follow_parent $sha1 $sha1_short $_revision - $_cp_remote $_upgrade $_q - $_authors %users/; + $sha1 $sha1_short $_revision + $_q $_authors %users/; $AUTHOR = 'Eric Wong '; $VERSION = '@@GIT_VERSION@@'; @@ -17,11 +13,8 @@ $Git::SVN::default_repo_id = 'git-svn'; $Git::SVN::default_ref_id = $ENV{GIT_SVN_ID} || 'git-svn'; -my $LC_ALL = $ENV{LC_ALL}; $Git::SVN::Log::TZ = $ENV{TZ}; -# make sure the svn binary gives consistent output between locales and TZs: $ENV{TZ} = 'UTC'; -$ENV{LC_ALL} = 'C'; $| = 1; # unbuffer STDOUT sub fatal (@) { print STDERR @_; exit 1 } @@ -60,19 +53,19 @@ BEGIN $sha1_short = qr/[a-f\d]{4,40}/; my ($_stdin, $_help, $_edit, $_repack, $_repack_nr, $_repack_flags, - $_message, $_file, $_no_metadata, + $_message, $_file, $_template, $_shared, - $_version, $_upgrade, + $_version, $_merge, $_strategy, $_dry_run, $_prefix); my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username, 'config-dir=s' => \$Git::SVN::Ra::config_dir, 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache ); -my %fc_opts = ( 'follow-parent|follow' => \$_follow_parent, +my %fc_opts = ( 'follow-parent|follow' => \$Git::SVN::_follow_parent, 'authors-file|A=s' => \$_authors, 'repack:i' => \$_repack, - 'no-metadata' => \$_no_metadata, + 'no-metadata' => \$Git::SVN::_no_metadata, 'quiet|q' => \$_q, 'repack-flags|repack-args|repack-opts=s' => \$_repack_flags, %remote_opts ); @@ -152,11 +145,10 @@ BEGIN my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd); read_repo_config(\%opts); -my $rv = GetOptions(%opts, 'help|H|h' => \$_help, - 'version|V' => \$_version, - 'minimize-connections' => - \$Git::SVN::Migration::_minimize, - 'id|i=s' => \$Git::SVN::default_ref_id); +my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version, + 'minimize-connections' => \$Git::SVN::Migration::_minimize, + 'id|i=s' => \$Git::SVN::default_ref_id, + 'svn-remote|remote|R=s' => \$Git::SVN::default_repo_id); exit 1 if (!$rv && $cmd ne 'log'); usage(0) if $_help; @@ -634,7 +626,7 @@ sub cmt_metadata { package Git::SVN; use strict; use warnings; -use vars qw/$default_repo_id $default_ref_id/; +use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent/; use Carp qw/croak/; use File::Path qw/mkpath/; use IPC::Open3; @@ -1012,9 +1004,11 @@ sub do_git_commit { defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec)) or croak $!; print $msg_fh $log_entry->{log} or croak $!; - print $msg_fh "\ngit-svn-id: ", $self->full_url, '@', - $log_entry->{revision}, ' ', - $self->ra->uuid, "\n" or croak $!; + unless ($_no_metadata) { + print $msg_fh "\ngit-svn-id: ", $self->full_url, '@', + $log_entry->{revision}, ' ', + $self->ra->uuid, "\n" or croak $!; + } $msg_fh->flush == 0 or croak $!; close $msg_fh or croak $!; chomp(my $commit = do { local $/; <$out_fh> }); @@ -1059,7 +1053,7 @@ sub match_paths { sub find_parent_branch { my ($self, $paths, $rev) = @_; - return undef unless $::_follow_parent; + return undef unless $_follow_parent; unless (defined $paths) { $self->ra->get_log([$self->{path}], $rev, $rev, 0, 1, 1, sub { $paths = dup_changed_paths($_[0]) }); @@ -1112,7 +1106,7 @@ sub find_parent_branch { $gs = Git::SVN->init($new_url, '', $ref_id, $ref_id); } my ($r0, $parent) = $gs->find_rev_before($r, 1); - if ($::_follow_parent && (!defined $r0 || !defined $parent)) { + if ($_follow_parent && (!defined $r0 || !defined $parent)) { $gs->fetch(0, $r); ($r0, $parent) = $gs->last_rev_commit; } From c7eba7163b452840c8492b9ad87846b44cc98ea7 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 31 Jan 2007 03:45:28 -0800 Subject: [PATCH 115/201] git-svn: gracefully handle --follow-parent failures We don't always know that a path will exist at a particular revision. Signed-off-by: Eric Wong --- git-svn.perl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/git-svn.perl b/git-svn.perl index cc5736d793..b2f86e84b4 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1055,8 +1055,11 @@ sub find_parent_branch { my ($self, $paths, $rev) = @_; return undef unless $_follow_parent; unless (defined $paths) { + my $err_handler = $SVN::Error::handler; + $SVN::Error::handler = \&Git::SVN::Ra::skip_unknown_revs; $self->ra->get_log([$self->{path}], $rev, $rev, 0, 1, 1, sub { $paths = dup_changed_paths($_[0]) }); + $SVN::Error::handler = $err_handler; } return undef unless defined $paths; From d4eff2bda5fc28559e96d62604ecaf78a4ff806b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 30 Jan 2007 14:04:22 -0800 Subject: [PATCH 116/201] git-svn: make (multi-)fetch safer but slower get_log with explicit paths is the safest way to get revisions that change a particular path we're interested in. Unfortunately that means we still have to run get_log multiple times for each path we're interested in, and even more if a path gets deleted. The first argument of get_log() is an array reference, but we shouldn't use more than one element in that array ref because the non-existence of _one_ of those paths for a particular range would cause an error for all paths in that range, so yes, we need multiple get_log calls to be on the safe side... Signed-off-by: Eric Wong --- git-svn.perl | 73 ++++++++++++++++++++++++++-------------------------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index b2f86e84b4..efc5515663 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1038,27 +1038,15 @@ sub revisions_eq { return 1; } -sub match_paths { - my ($self, $paths) = @_; - return 1 if $paths->{'/'}; - $self->{path_regex} ||= qr/^\/\Q$self->{path}\E\/?/; - grep /$self->{path_regex}/, keys %$paths and return 1; - my $c = ''; - foreach (split m#/#, $self->rel_path) { - $c .= "/$_"; - return 1 if $paths->{$c}; - } - return 0; -} - sub find_parent_branch { my ($self, $paths, $rev) = @_; return undef unless $_follow_parent; unless (defined $paths) { my $err_handler = $SVN::Error::handler; $SVN::Error::handler = \&Git::SVN::Ra::skip_unknown_revs; - $self->ra->get_log([$self->{path}], $rev, $rev, 0, 1, 1, - sub { $paths = dup_changed_paths($_[0]) }); + $self->ra->get_log([$self->{path}], $rev, $rev, 0, 1, 1, sub { + $paths = + Git::SVN::Ra::dup_changed_paths($_[0]) }); $SVN::Error::handler = $err_handler; } return undef unless defined $paths; @@ -1134,7 +1122,6 @@ sub find_parent_branch { or die "SVN connection failed somewhere...\n"; } print STDERR "Successfully followed parent\n"; - $ed->{new_fetch} = 1; return $self->make_log_entry($rev, [$parent], $ed); } not_found: @@ -1170,7 +1157,6 @@ sub do_fetch { return $log_entry; } $ed = SVN::Git::Fetcher->new($self); - $ed->{new_fetch} = 1; } unless ($self->ra->gs_do_update($last_rev, $rev, $self, $ed)) { die "SVN connection failed somewhere...\n"; @@ -1243,8 +1229,6 @@ sub make_log_entry { my ($self, $rev, $parents, $ed) = @_; my $untracked = $self->get_untracked($ed); - return undef if (! $ed->{new_fetch} && ! $ed->{nr} && ! @$untracked); - open my $un, '>>', "$self->{dir}/unhandled.log" or croak $!; print $un "r$rev\n" or croak $!; print $un $_, "\n" foreach @$untracked; @@ -2314,38 +2298,53 @@ sub gs_fetch_loop_common { my ($self, $base, $head, @gs) = @_; my $inc = 1000; my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc); - my @paths = @gs == 1 ? ($gs[0]->{path}) : (''); foreach my $gs (@gs) { if (my $last_commit = $gs->last_commit) { $gs->assert_index_clean($last_commit); } } while (1) { - my @revs; + my %revs; my $err; my $err_handler = $SVN::Error::handler; $SVN::Error::handler = sub { ($err) = @_; skip_unknown_revs($err); }; - $self->get_log(\@paths, $min, $max, 0, 1, 1, - sub { push @revs, [ dup_changed_paths($_[0]), $_[1] ]; }); - $SVN::Error::handler = $err_handler; + foreach my $gs (@gs) { + $self->get_log([$gs->{path}], $min, $max, 0, 1, 1, sub + { my ($paths, $rev) = @_; + push @{$revs{$rev}}, + [ $gs, + dup_changed_paths($paths) ] }); - if (! @revs && $err && $max >= $head) { - print STDERR "Branch probably deleted:\n ", - $err->expanded_message, - "\nWill attempt to follow revisions ", - "r$min .. r$max ", - "committed before the deletion\n"; - @revs = map { [ undef, $_ ] } ($min .. $max); - } - foreach (@revs) { - my ($paths, $r) = @$_; - foreach my $gs (@gs) { - if ($paths) { - $gs->match_paths($paths) or next; + next unless ($err && $max >= $head); + + print STDERR "Path '$gs->{path}' ", + "was probably deleted:\n", + $err->expanded_message, + "\nWill attempt to follow ", + "revisions r$min .. r$max ", + "committed before the deletion\n"; + my $hi = $max; + while (--$hi >= $min) { + my $ok; + $self->get_log([$gs->{path}], $min, $hi, + 0, 1, 1, sub { + my ($paths, $rev) = @_; + $ok = $rev; + push @{$revs{$rev}}, [ $gs, + dup_changed_paths($_[0])]}); + if ($ok) { + print STDERR "r$min .. r$ok OK\n"; + last; } + } + } + $SVN::Error::handler = $err_handler; + foreach my $r (sort {$a <=> $b} keys %revs) { + foreach (@{$revs{$r}}) { + my ($gs, $paths) = @$_; my $lr = $gs->last_rev; next if defined $lr && $lr >= $r; next if defined $gs->rev_db_get($r); From 47a0b75e01876b6e138d78c9504eebedd45c1283 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 31 Jan 2007 05:13:30 -0800 Subject: [PATCH 117/201] git-svn: avoid a huge memory spike with high-numbered revisions Passing very large strings as arguments is bad for memory usage as it never seems to get freed in Perl. The .rev_db format is already not optimized for projects with sparse history. Signed-off-by: Eric Wong --- git-svn.perl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index efc5515663..ddb0382608 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1345,8 +1345,9 @@ sub rev_db_set { seek $fh, 0, 2 or croak $!; my $pos = tell $fh; if ($pos < $offset) { - print $fh (('0' x 40),"\n") x (($offset - $pos) / 41) - or croak $!; + for (1 .. (($offset - $pos) / 41)) { + print $fh (('0' x 40),"\n") or croak $!; + } } seek $fh, $offset, 0 or croak $!; print $fh $commit,"\n" or croak $!; From ecc712ddc41999e5f082cb69406d30caa062c6b9 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 31 Jan 2007 12:28:10 -0800 Subject: [PATCH 118/201] git-svn: re-enable repacking flags Signed-off-by: Eric Wong --- git-svn.perl | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index ddb0382608..1fd65526d1 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -52,7 +52,6 @@ BEGIN $sha1 = qr/[a-f\d]{40}/; $sha1_short = qr/[a-f\d]{4,40}/; my ($_stdin, $_help, $_edit, - $_repack, $_repack_nr, $_repack_flags, $_message, $_file, $_template, $_shared, $_version, @@ -64,10 +63,11 @@ BEGIN 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache ); my %fc_opts = ( 'follow-parent|follow' => \$Git::SVN::_follow_parent, 'authors-file|A=s' => \$_authors, - 'repack:i' => \$_repack, + 'repack:i' => \$Git::SVN::_repack, 'no-metadata' => \$Git::SVN::_no_metadata, 'quiet|q' => \$_q, - 'repack-flags|repack-args|repack-opts=s' => \$_repack_flags, + 'repack-flags|repack-args|repack-opts=s' => + \$Git::SVN::_repack_flags, %remote_opts ); my ($_trunk, $_tags, $_branches); @@ -158,6 +158,7 @@ BEGIN unless ($cmd =~ /^(?:init|multi-init|commit-diff)$/) { Git::SVN::Migration::migration_check(); } +Git::SVN::init_vars(); eval { Git::SVN::verify_remotes_sanity(); $cmd{$cmd}->[0]->(@ARGV); @@ -626,11 +627,13 @@ sub cmt_metadata { package Git::SVN; use strict; use warnings; -use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent/; +use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent + $_repack $_repack_flags/; use Carp qw/croak/; use File::Path qw/mkpath/; use IPC::Open3; +my $_repack_nr; # properties that we do not log: my %SKIP_PROP; BEGIN { @@ -676,6 +679,14 @@ sub read_all_remotes { $r; } +sub init_vars { + if (defined $_repack) { + $_repack = 1000 if ($_repack <= 0); + $_repack_nr = $_repack; + $_repack_flags ||= '-d'; + } +} + sub verify_remotes_sanity { return unless -d $ENV{GIT_DIR}; my %seen; @@ -1025,6 +1036,13 @@ sub do_git_commit { $self->{last_rev} = $log_entry->{revision}; $self->{last_commit} = $commit; print "r$log_entry->{revision} = $commit ($self->{ref_id})\n"; + if (defined $_repack && (--$_repack_nr == 0)) { + $_repack_nr = $_repack; + # repack doesn't use any arguments with spaces in them, does it? + print "Running git repack $_repack_flags ...\n"; + command_noisy('repack', split(/\s+/, $_repack_flags)); + print "Done repacking\n"; + } return $commit; } From 373274f978a48b62549f20059bff630d85533533 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 31 Jan 2007 13:54:23 -0800 Subject: [PATCH 119/201] git-svn: do our best to ensure that our ref and rev_db are consistent Defer any signals that cause termination while they are updating; and put the update-ref call as close to the rename() as possible. Also, make things extra-safe (but slower) for people using --no-metadata since they can't rely on .rev_db being rebuilt if it's clobbered (well, I'm calling update-ref with the -m flag for reflogs, we don't yet have a way to rebuild .rev_db from reflogs. Signed-off-by: Eric Wong --- git-svn.perl | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 1fd65526d1..2206f1b250 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -631,6 +631,7 @@ package Git::SVN; $_repack $_repack_flags/; use Carp qw/croak/; use File::Path qw/mkpath/; +use File::Copy qw/copy/; use IPC::Open3; my $_repack_nr; @@ -645,6 +646,9 @@ BEGIN svn:entry:committed-date/; } +my %LOCKFILES; +END { unlink keys %LOCKFILES if %LOCKFILES } + sub fetch_all { my ($repo_id, $url, $fetch) = @_; my @gs; @@ -1030,8 +1034,7 @@ sub do_git_commit { die "Failed to commit, invalid sha1: $commit\n"; } - command_noisy('update-ref',$self->refname, $commit); - $self->rev_db_set($log_entry->{revision}, $commit); + $self->rev_db_set($log_entry->{revision}, $commit, 1); $self->{last_rev} = $log_entry->{revision}; $self->{last_commit} = $commit; @@ -1353,11 +1356,28 @@ sub rebuild { # to a revision: (41 * rev) is the byte offset. # A record of 40 0s denotes an empty revision. # And yes, it's still pretty fast (faster than Tie::File). +# These files are disposable unless --no-metadata is set sub rev_db_set { - my ($self, $rev, $commit) = @_; + my ($self, $rev, $commit, $update_ref) = @_; length $commit == 40 or croak "arg3 must be a full SHA1 hexsum\n"; - open my $fh, '+<', $self->{db_path} or croak $!; + my ($db, $db_lock) = ($self->{db_path}, "$self->{db_path}.lock"); + my $sig; + if ($update_ref) { + $SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} = + $SIG{USR1} = $SIG{USR2} = sub { $sig = $_[0] }; + } + $LOCKFILES{$db_lock} = 1; + if ($_no_metadata) { + copy($db, $db_lock) or die "rev_db_set(@_): ", + "Failed to copy: ", + "$db => $db_lock ($!)\n"; + } else { + rename $db, $db_lock or die "rev_db_set(@_): ", + "Failed to rename: ", + "$db => $db_lock ($!)\n"; + } + open my $fh, '+<', $db_lock or croak $!; my $offset = $rev * 41; # assume that append is the common case: seek $fh, 0, 2 or croak $!; @@ -1370,6 +1390,18 @@ sub rev_db_set { seek $fh, $offset, 0 or croak $!; print $fh $commit,"\n" or croak $!; close $fh or croak $!; + if ($update_ref) { + command_noisy('update-ref', '-m', "r$rev", + $self->refname, $commit); + } + rename $db_lock, $db or die "rev_db_set(@_): ", "Failed to rename: ", + "$db_lock => $db ($!)\n"; + delete $LOCKFILES{$db_lock}; + if ($update_ref) { + $SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} = + $SIG{USR1} = $SIG{USR2} = 'DEFAULT'; + kill $sig, $$ if defined $sig; + } } sub rev_db_get { From 9c93fee51e26d5db82414317fa169294f3fa94b0 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 31 Jan 2007 17:22:31 -0800 Subject: [PATCH 120/201] git-svn: avoid redundant get_log calls between invocations Prefill .rev_db to the maximum revision we tried to fetch; and take advantage of that so we can avoid using get_log() on ranges we've already seen (and have deemed uninteresting). Signed-off-by: Eric Wong --- git-svn.perl | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 2206f1b250..b1d91fa471 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -655,18 +655,14 @@ sub fetch_all { my $ra = Git::SVN::Ra->new($url); my $head = $ra->get_latest_revnum; my $base = $head; - my $new_remote; foreach my $p (sort keys %$fetch) { my $gs = Git::SVN->new($fetch->{$p}, $repo_id, $p); - my $lr = $gs->last_rev; + my $lr = $gs->rev_db_max; if (defined $lr) { $base = $lr if ($lr < $base); - } else { - $new_remote = 1; } push @gs, $gs; } - $base = 0 if $new_remote; return if (++$base > $head); $ra->gs_fetch_loop_common($base, $head, @gs); } @@ -899,13 +895,17 @@ sub last_rev_commit { $rl = readline $fh; defined $rl or return (undef, undef); chomp $rl; - while ($c ne $rl && tell $fh != 0) { + while (('0' x40) eq $rl && tell $fh != 0) { $offset -= 41; seek $fh, $offset, 2; $rl = readline $fh; defined $rl or return (undef, undef); chomp $rl; } + if ($c) { + die "$self->{db_path} and ", $self->refname, + " inconsistent!:\n$c != $rl\n"; + } my $rev = tell $fh; croak $! if ($rev < 0); $rev = ($rev - 41) / 41; @@ -917,7 +917,7 @@ sub last_rev_commit { sub get_fetch_range { my ($self, $min, $max) = @_; $max ||= $self->ra->get_latest_revnum; - $min ||= $self->last_rev || 0; + $min ||= $self->rev_db_max; (++$min, $max); } @@ -1404,6 +1404,16 @@ sub rev_db_set { } } +sub rev_db_max { + my ($self) = @_; + my @stat = stat $self->{db_path} or + die "Couldn't stat $self->{db_path}: $!\n"; + ($stat[7] % 41) == 0 or + die "$self->{db_path} inconsistent size:$stat[7]\n"; + my $max = $stat[7] / 41; + (($max > 0) ? $max - 1 : 0); +} + sub rev_db_get { my ($self, $rev) = @_; my $ret; @@ -2404,6 +2414,12 @@ sub gs_fetch_loop_common { } } } + # pre-fill the .rev_db since it'll eventually get filled in + # with '0' x40 if something new gets committed + foreach my $gs (@gs) { + next if defined $gs->rev_db_get($max); + $gs->rev_db_set($max, 0 x40); + } last if $max >= $head; $min = $max + 1; $max += $inc; From ce4b4af7ff36d4e4999da937dffd2f9a3a420277 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 31 Jan 2007 17:57:36 -0800 Subject: [PATCH 121/201] git-svn: use sys* IO functions for reading rev_db Using buffered IO for reading 40-41 bytes at a time isn't very efficient. Buffering writes for a short duration is alright since we close() right away and buffers will be flushed. Signed-off-by: Eric Wong --- git-svn.perl | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index b1d91fa471..1e3a3c08f1 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -891,23 +891,20 @@ sub last_rev_commit { my $rl; open my $fh, '<', $self->{db_path} or croak "$self->{db_path} not readable: $!\n"; - seek $fh, $offset, 2; - $rl = readline $fh; - defined $rl or return (undef, undef); + sysseek($fh, $offset, 2); # don't care for errors + sysread($fh, $rl, 41) == 41 or return (undef, undef); chomp $rl; - while (('0' x40) eq $rl && tell $fh != 0) { + while (('0' x40) eq $rl && sysseek($fh, 0, 1) != 0) { $offset -= 41; - seek $fh, $offset, 2; - $rl = readline $fh; - defined $rl or return (undef, undef); + sysseek($fh, $offset, 2); # don't care for errors + sysread($fh, $rl, 41) == 41 or return (undef, undef); chomp $rl; } if ($c) { die "$self->{db_path} and ", $self->refname, " inconsistent!:\n$c != $rl\n"; } - my $rev = tell $fh; - croak $! if ($rev < 0); + my $rev = sysseek($fh, 0, 1) or croak $!; $rev = ($rev - 41) / 41; close $fh or croak $!; ($self->{last_rev}, $self->{last_commit}) = ($rev, $c); @@ -1419,12 +1416,9 @@ sub rev_db_get { my $ret; my $offset = $rev * 41; open my $fh, '<', $self->{db_path} or croak $!; - if (seek $fh, $offset, 0) { - $ret = readline $fh; - if (defined $ret) { - chomp $ret; - $ret = undef if ($ret =~ /^0{40}$/); - } + if (sysseek($fh, $offset, 0) == $offset) { + my $read = sysread($fh, $ret, 40); + $ret = undef if ($read != 40 || $ret eq ('0'x40)); } close $fh or croak $!; $ret; From d8115c5104dbee29433a7f33a3e0d3e1738a581e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 1 Feb 2007 03:30:31 -0800 Subject: [PATCH 122/201] git-svn: don't write to the config file from --follow-parent Having 'fetch' entries in the config file created from --follow-parent is wasteful because it can cause *future* of invocations to follow revisions we were never interested in in the first place. Signed-off-by: Eric Wong --- git-svn.perl | 20 +++++++++++--------- t/t9104-git-svn-follow-parent.sh | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 1e3a3c08f1..a2db73fe00 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -724,7 +724,7 @@ sub find_existing_remote { } sub init_remote_config { - my ($self, $url) = @_; + my ($self, $url, $no_write) = @_; $url =~ s!/+$!!; # strip trailing slash my $r = read_all_remotes(); my $existing = find_existing_remote($url, $r); @@ -769,19 +769,21 @@ sub init_remote_config { die "svn-remote.$xrepo_id.fetch already set to track ", "$xpath:refs/remotes/", $self->refname, "\n"; } - command_noisy('config', - "svn-remote.$self->{repo_id}.url", $url); - command_noisy('config', '--add', - "svn-remote.$self->{repo_id}.fetch", - "$self->{path}:".$self->refname); + unless ($no_write) { + command_noisy('config', + "svn-remote.$self->{repo_id}.url", $url); + command_noisy('config', '--add', + "svn-remote.$self->{repo_id}.fetch", + "$self->{path}:".$self->refname); + } $self->{url} = $url; } sub init { - my ($class, $url, $path, $repo_id, $ref_id) = @_; + my ($class, $url, $path, $repo_id, $ref_id, $no_write) = @_; my $self = _new($class, $repo_id, $ref_id, $path); if (defined $url) { - $self->init_remote_config($url); + $self->init_remote_config($url, $no_write); } $self; } @@ -1112,7 +1114,7 @@ sub find_parent_branch { # just grow a tail if we're not unique enough :x $ref_id .= '-' while find_ref($ref_id); print STDERR "Initializing parent: $ref_id\n"; - $gs = Git::SVN->init($new_url, '', $ref_id, $ref_id); + $gs = Git::SVN->init($new_url, '', $ref_id, $ref_id, 1); } my ($r0, $parent) = $gs->find_rev_before($r, 1); if ($_follow_parent && (!defined $r0 || !defined $parent)) { diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 41b9c19d45..7c852c1d7f 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -34,7 +34,7 @@ test_expect_success 'init and fetch --follow-parent a moved directory' " = \"\`git-rev-parse --verify refs/remotes/thunk~1\`\" && test \"\`git-cat-file blob refs/remotes/thunk:readme |\ sed -n -e '3p'\`\" = goodbye && - test -n \"\`git-config --get svn-remote.git-svn.fetch \ + test -z \"\`git-config --get svn-remote.git-svn.fetch \ '^trunk:refs/remotes/thunk@2$'\`\" " From 88cf4107eb70cdcdc226f2385a3ee54fb428c41d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 1 Feb 2007 03:59:07 -0800 Subject: [PATCH 123/201] git-svn: save paths to tags/branches with for future reuse Signed-off-by: Eric Wong --- git-svn.perl | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/git-svn.perl b/git-svn.perl index a2db73fe00..ad2ef53f8d 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -469,6 +469,8 @@ sub complete_url_ls_init { my $r = defined $_revision ? $_revision : $ra->get_latest_revnum; my ($dirent, undef, undef) = $ra->get_dir($repo_path, $r); my $url = $ra->{url}; + my $remote_id; + my $remote_path; foreach my $d (sort keys %$dirent) { next if ($dirent->{$d}->kind != $SVN::Node::dir); my $path = "$repo_path/$d"; @@ -477,8 +479,17 @@ sub complete_url_ls_init { # don't try to init already existing refs unless ($gs) { print "init $url/$path => $ref\n"; - Git::SVN->init($url, $path, undef, $ref); + $gs = Git::SVN->init($url, $path, undef, $ref); } + $remote_id ||= $gs->{repo_id} if $gs; + } + if (defined $remote_id) { + $remote_path = "$ra->{svn_path}/$repo_path/*"; + $remote_path =~ s#/+#/#g; + $remote_path =~ s#^/##g; + my ($n) = ($switch =~ /^--(\w+)/); + command_noisy('config', "svn-remote.$remote_id.$n", + $remote_path); } } From 471bc000528cf49928dae8872609b7fefc0c59ee Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 1 Feb 2007 04:06:27 -0800 Subject: [PATCH 124/201] git-svn: migrations default to [svn-remote "git-svn"] It looks better (like [remote "origin"]) instead of whatever refname came up first in our directory traversal. Of course --remote= overrides this. Signed-off-by: Eric Wong --- git-svn.perl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/git-svn.perl b/git-svn.perl index ad2ef53f8d..de14ed4357 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2945,7 +2945,10 @@ sub migrate_from_v2 { my $migrated = 0; foreach my $ref_id (sort keys %l_map) { - Git::SVN->init($l_map{$ref_id}, '', $ref_id, $ref_id); + eval { Git::SVN->init($l_map{$ref_id}, '', undef, $ref_id) }; + if ($@) { + Git::SVN->init($l_map{$ref_id}, '', $ref_id, $ref_id); + } $migrated++; } $migrated; From ef70de968509c447f5c02f4ba99f1cf0cadf5c1f Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 1 Feb 2007 04:12:41 -0800 Subject: [PATCH 125/201] git-svn: get rid of revisions_eq check for --follow-parent This was originally needed before we used the delta fetcher and had a less-clean follow-parent implementation that could leave holes in the history. Signed-off-by: Eric Wong --- git-svn.perl | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index de14ed4357..58d0600f89 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1059,16 +1059,6 @@ sub do_git_commit { return $commit; } -sub revisions_eq { - my ($self, $r0, $r1) = @_; - return 1 if $r0 == $r1; - my $nr = 0; - $self->ra->get_log([$self->{path}], $r0, $r1, - 0, 0, 1, sub { $nr++ }); - return 0 if ($nr > 1); - return 1; -} - sub find_parent_branch { my ($self, $paths, $rev) = @_; return undef unless $_follow_parent; @@ -1132,7 +1122,7 @@ sub find_parent_branch { $gs->fetch(0, $r); ($r0, $parent) = $gs->last_rev_commit; } - if (defined $r0 && defined $parent && $gs->revisions_eq($r0, $r)) { + if (defined $r0 && defined $parent) { print STDERR "Found branch parent: ($self->{ref_id}) $parent\n"; $self->assert_index_clean($parent); my $ed; From 502c1bf629154b4a248105b10346a06a6ff07387 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 1 Feb 2007 04:26:00 -0800 Subject: [PATCH 126/201] git-svn: avoid extra get_log calls when refspecs are added for fetching Since fetch_loop_common starts from the lowest revision number in a group of Git::SVN objects; we want to avoid refetching get_log for current users for things we've already cut it. Signed-off-by: Eric Wong --- git-svn.perl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/git-svn.perl b/git-svn.perl index 58d0600f89..8c24012e7b 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2370,7 +2370,12 @@ sub gs_fetch_loop_common { skip_unknown_revs($err); }; foreach my $gs (@gs) { - $self->get_log([$gs->{path}], $min, $max, 0, 1, 1, sub + my $min_r = $min; + my $rdb_max = $gs->rev_db_max; + next if $rdb_max >= $max; + $min_r = $rdb_max + 1 if ($rdb_max > $min_r); + $self->get_log([$gs->{path}], $min_r, $max, + 0, 1, 1, sub { my ($paths, $rev) = @_; push @{$revs{$rev}}, [ $gs, From 9fa00b655cfd67bf344668a0d913f90ec9a8141d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 3 Feb 2007 12:49:48 -0800 Subject: [PATCH 127/201] git-svn: just name the default svn-remote "svn" instead of "git-svn" It can be confusing and redundant, since historically the default remote ref (not remote itself) has been "git-svn", too. Signed-off-by: Eric Wong --- git-svn.perl | 2 +- t/t9100-git-svn-basic.sh | 8 ++++---- t/t9104-git-svn-follow-parent.sh | 10 +++++----- t/t9107-git-svn-migrate.sh | 12 ++++++------ 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 8c24012e7b..66b4c20fd9 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -10,7 +10,7 @@ $VERSION = '@@GIT_VERSION@@'; $ENV{GIT_DIR} ||= '.git'; -$Git::SVN::default_repo_id = 'git-svn'; +$Git::SVN::default_repo_id = 'svn'; $Git::SVN::default_ref_id = $ENV{GIT_SVN_ID} || 'git-svn'; $Git::SVN::Log::TZ = $ENV{TZ}; diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index 3dc4de2fad..8b6c8ffe10 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -214,7 +214,7 @@ EOF test_expect_success "$name" "diff -u a expected" test_expect_failure 'exit if remote refs are ambigious' " - git-repo-config --add svn-remote.git-svn.fetch \ + git-repo-config --add svn-remote.svn.fetch \ bar:refs/remotes/git-svn && git-svn migrate " @@ -222,7 +222,7 @@ test_expect_failure 'exit if remote refs are ambigious' " test_expect_failure 'exit if init-ing a would clobber a URL' " svnadmin create ${PWD}/svnrepo2 && svn mkdir -m 'mkdir bar' ${svnrepo}2/bar && - git-repo-config --unset svn-remote.git-svn.fetch \ + git-repo-config --unset svn-remote.svn.fetch \ '^bar:refs/remotes/git-svn$' && git-svn init ${svnrepo}2/bar " @@ -230,9 +230,9 @@ test_expect_failure 'exit if init-ing a would clobber a URL' " test_expect_success \ 'init allows us to connect to another directory in the same repo' " git-svn init -i bar $svnrepo/bar && - git repo-config --get svn-remote.git-svn.fetch \ + git repo-config --get svn-remote.svn.fetch \ '^bar:refs/remotes/bar$' && - git repo-config --get svn-remote.git-svn.fetch \ + git repo-config --get svn-remote.svn.fetch \ '^:refs/remotes/git-svn$' " diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 7c852c1d7f..eebb84974c 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -34,15 +34,15 @@ test_expect_success 'init and fetch --follow-parent a moved directory' " = \"\`git-rev-parse --verify refs/remotes/thunk~1\`\" && test \"\`git-cat-file blob refs/remotes/thunk:readme |\ sed -n -e '3p'\`\" = goodbye && - test -z \"\`git-config --get svn-remote.git-svn.fetch \ + test -z \"\`git-config --get svn-remote.svn.fetch \ '^trunk:refs/remotes/thunk@2$'\`\" " test_expect_success 'init and fetch from one svn-remote' " - git-repo-config svn-remote.git-svn.url $svnrepo && - git-repo-config --add svn-remote.git-svn.fetch \ + git-repo-config svn-remote.svn.url $svnrepo && + git-repo-config --add svn-remote.svn.fetch \ trunk:refs/remotes/svn/trunk && - git-repo-config --add svn-remote.git-svn.fetch \ + git-repo-config --add svn-remote.svn.fetch \ thunk:refs/remotes/svn/thunk && git-svn fetch --follow-parent -i svn/thunk && test \"\`git-rev-parse --verify refs/remotes/svn/trunk\`\" \ @@ -54,7 +54,7 @@ test_expect_success 'init and fetch from one svn-remote' " test_expect_success 'follow deleted parent' " svn cp -m 'resurrecting trunk as junk' \ -r2 $svnrepo/trunk $svnrepo/junk && - git-repo-config --add svn-remote.git-svn.fetch \ + git-repo-config --add svn-remote.svn.fetch \ junk:refs/remotes/svn/junk && git-svn fetch --follow-parent -i svn/thunk && git-svn fetch -i svn/junk --follow-parent && diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index f6d84ba7a5..0fbfd264ec 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -34,14 +34,14 @@ test_expect_success 'initialize old-style (v0) git-svn layout' " ! test -d $GIT_DIR/git-svn && git-rev-parse --verify refs/remotes/git-svn^0 && git-rev-parse --verify refs/remotes/svn^0 && - test \`git repo-config --get svn-remote.git-svn.url\` = '$svnrepo' && - test \`git repo-config --get svn-remote.git-svn.fetch\` = \ + test \`git repo-config --get svn-remote.svn.url\` = '$svnrepo' && + test \`git repo-config --get svn-remote.svn.fetch\` = \ ':refs/remotes/git-svn' " test_expect_success 'initialize a multi-repository repo' " git-svn multi-init $svnrepo -T trunk -t tags -b branches && - git-repo-config --get-all svn-remote.git-svn.fetch > fetch.out && + git-repo-config --get-all svn-remote.svn.fetch > fetch.out && grep '^trunk:refs/remotes/trunk$' fetch.out && grep '^branches/a:refs/remotes/a$' fetch.out && grep '^branches/b:refs/remotes/b$' fetch.out && @@ -65,8 +65,8 @@ test_expect_success 'multi-fetch works on partial urls + paths' " " test_expect_success 'migrate --minimize on old multi-inited layout' " - git repo-config --unset-all svn-remote.git-svn.fetch && - git repo-config --unset-all svn-remote.git-svn.url && + git repo-config --unset-all svn-remote.svn.fetch && + git repo-config --unset-all svn-remote.svn.url && rm -rf $GIT_DIR/svn && for i in \`cat fetch.out\`; do path=\`expr \$i : '\\([^:]*\\):.*$'\` @@ -78,7 +78,7 @@ test_expect_success 'migrate --minimize on old multi-inited layout' " done && git-svn migrate --minimize && test -z \"\`git-repo-config -l |grep -v '^svn-remote\.git-svn\.'\`\" && - git-repo-config --get-all svn-remote.git-svn.fetch > fetch.out && + git-repo-config --get-all svn-remote.svn.fetch > fetch.out && grep '^trunk:refs/remotes/trunk$' fetch.out && grep '^branches/a:refs/remotes/a$' fetch.out && grep '^branches/b:refs/remotes/b$' fetch.out && From 4bb9ed0466e9ca6b72b3d9c454a743aea862b1f4 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 3 Feb 2007 13:29:17 -0800 Subject: [PATCH 128/201] git-svn: prepare multi-init for wildcard support Update the tests since we no longer write so many things to the config. Signed-off-by: Eric Wong --- git-svn.perl | 124 ++++++++++++++++++++++++++++++++++--- t/t9107-git-svn-migrate.sh | 20 ++++-- 2 files changed, 131 insertions(+), 13 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 66b4c20fd9..6874eabffa 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -367,9 +367,10 @@ sub cmd_multi_init { sub cmd_multi_fetch { my $remotes = Git::SVN::read_all_remotes(); foreach my $repo_id (sort keys %$remotes) { - my $url = $remotes->{$repo_id}->{url} or next; - my $fetch = $remotes->{$repo_id}->{fetch} or next; - Git::SVN::fetch_all($repo_id, $url, $fetch); + if ($remotes->{$repo_id}->{url} && + $remotes->{$repo_id}->{fetch}) { + Git::SVN::fetch_all($repo_id, $remotes); + } } } @@ -479,9 +480,12 @@ sub complete_url_ls_init { # don't try to init already existing refs unless ($gs) { print "init $url/$path => $ref\n"; - $gs = Git::SVN->init($url, $path, undef, $ref); + $gs = Git::SVN->init($url, $path, undef, $ref, 1); + } + if ($gs) { + $remote_id = $gs->{repo_id}; + last; } - $remote_id ||= $gs->{repo_id} if $gs; } if (defined $remote_id) { $remote_path = "$ra->{svn_path}/$repo_path/*"; @@ -489,7 +493,7 @@ sub complete_url_ls_init { $remote_path =~ s#^/##g; my ($n) = ($switch =~ /^--(\w+)/); command_noisy('config', "svn-remote.$remote_id.$n", - $remote_path); + "$remote_path:refs/remotes/$pfx*"); } } @@ -660,9 +664,48 @@ BEGIN my %LOCKFILES; END { unlink keys %LOCKFILES if %LOCKFILES } +sub resolve_local_globs { + my ($url, $fetch, $glob_spec) = @_; + return unless defined $glob_spec; + my $ref = $glob_spec->{ref}; + my $path = $glob_spec->{path}; + foreach (command(qw#for-each-ref --format=%(refname) refs/remotes#)) { + next unless m#^refs/remotes/$ref->{regex}$#; + my $p = $1; + my $pathname = $path->full_path($p); + my $refname = $ref->full_path($p); + if (my $existing = $fetch->{$pathname}) { + if ($existing ne $refname) { + die "Refspec conflict:\n", + "existing: refs/remotes/$existing\n", + " globbed: refs/remotes/$refname\n"; + } + my $u = (::cmt_metadata("refs/remotes/$refname"))[0]; + $u =~ s!^\Q$url\E/?!! or die + "refs/remotes/$refname: '$url' not found in '$u'\n"; + if ($pathname ne $u) { + warn "W: Refspec glob conflict ", + "(ref: refs/remotes/$refname):\n", + "expected path: $pathname\n", + " real path: $u\n", + "Continuing ahead with $u\n"; + next; + } + } else { + warn "Globbed ($path->{glob}:$ref->{glob}): ", + "$pathname == $refname\n"; + $fetch->{$pathname} = $refname; + } + } +} + sub fetch_all { - my ($repo_id, $url, $fetch) = @_; + my ($repo_id, $remotes) = @_; + my $fetch = $remotes->{$repo_id}->{fetch}; + my $url = $remotes->{$repo_id}->{url}; my @gs; + resolve_local_globs($url, $fetch, $remotes->{$repo_id}->{branches}); + resolve_local_globs($url, $fetch, $remotes->{$repo_id}->{tags}); my $ra = Git::SVN::Ra->new($url); my $head = $ra->get_latest_revnum; my $base = $head; @@ -685,6 +728,16 @@ sub read_all_remotes { $r->{$1}->{fetch}->{$2} = $3; } elsif (m!^(.+)\.url=\s*(.*)\s*$!) { $r->{$1}->{url} = $2; + } elsif (m!^(.+)\.(branches|tags)= + (.*):refs/remotes/(.+)\s*$/!x) { + my ($p, $g) = ($3, $4); + my $rs = $r->{$1}->{$2} = { + path => Git::SVN::GlobSpec->new($p), + ref => Git::SVN::GlobSpec->new($g) }; + if (length($rs->{ref}->{right}) != 0) { + die "The '*' glob character must be the last ", + "character of '$g'\n"; + } } } $r; @@ -3072,10 +3125,67 @@ sub DESTROY { command_close_pipe($self->{gui}, $self->{ctx}); } +package Git::SVN::GlobSpec; +use strict; +use warnings; + +sub new { + my ($class, $glob) = @_; + warn "glob: $glob\n"; + my $re = $glob; + $re =~ s!/+$!!g; # no need for trailing slashes + my $nr = ($re =~ s!^(.*/?)\*(/?.*)$!\(\[^/\]+\)!g); + my ($left, $right) = ($1, $2); + if ($nr > 1) { + warn "Only one '*' wildcard expansion ", + "is supported (got $nr): '$glob'\n"; + } elsif ($nr == 0) { + warn "One '*' is needed for glob: '$glob'\n"; + } + $re = quotemeta($left) . $re . quotemeta($right); + $left =~ s!/+$!!g; + $right =~ s!^/+!!g; + bless { left => $left, right => $right, + regex => qr/$re/, glob => $glob }, $class; +} + +sub full_path { + my ($self, $path) = @_; + return (length $self->{left} ? "$self->{left}/" : '') . + $path . (length $self->{right} ? "/$self->{right}" : ''); +} + __END__ Data structures: + +$remotes = { # returned by read_all_remotes() + 'svn' => { + # svn-remote.svn.url=https://svn.musicpd.org + url => 'https://svn.musicpd.org', + # svn-remote.svn.fetch=mpd/trunk:trunk + fetch => { + 'mpd/trunk' => 'trunk', + }, + # svn-remote.svn.tags=mpd/tags/*:tags/* + tags => { + path => { + left => 'mpd/tags', + right => '', + regex => qr!mpd/tags/([^/]+)$!, + glob => 'tags/*', + }, + ref => { + left => 'tags', + right => '', + regex => qr!tags/([^/]+)$!, + glob => 'tags/*', + }, + } + } +}; + $log_entry hashref as returned by libsvn_log_entry() { log => 'whitespace-formatted log entry diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index 0fbfd264ec..8376429bcb 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -6,7 +6,7 @@ test_description='git-svn metadata migrations from previous versions' test_expect_success 'setup old-looking metadata' " cp $GIT_DIR/config $GIT_DIR/config-old-git-svn && mkdir import && - cd import + cd import && for i in trunk branches/a branches/b \ tags/0.1 tags/0.2 tags/0.3; do mkdir -p \$i && \ @@ -43,11 +43,19 @@ test_expect_success 'initialize a multi-repository repo' " git-svn multi-init $svnrepo -T trunk -t tags -b branches && git-repo-config --get-all svn-remote.svn.fetch > fetch.out && grep '^trunk:refs/remotes/trunk$' fetch.out && - grep '^branches/a:refs/remotes/a$' fetch.out && - grep '^branches/b:refs/remotes/b$' fetch.out && - grep '^tags/0\.1:refs/remotes/tags/0\.1$' fetch.out && - grep '^tags/0\.2:refs/remotes/tags/0\.2$' fetch.out && - grep '^tags/0\.3:refs/remotes/tags/0\.3$' fetch.out + test -n \"\`git-config --get svn-remote.svn.branches \ + '^branches/\*:refs/remotes/\*$'\`\" && + test -n \"\`git-config --get svn-remote.svn.tags \ + '^tags/\*:refs/remotes/tags/\*$'\`\" && + git config --unset svn-remote.svn.branches \ + '^branches/\*:refs/remotes/\*$' && + git config --unset svn-remote.svn.tags \ + '^tags/\*:refs/remotes/tags/\*$' && + git-config --add svn-remote.svn.fetch 'branches/a:refs/remotes/a' && + git-config --add svn-remote.svn.fetch 'branches/b:refs/remotes/b' && + for i in tags/0.1 tags/0.2 tags/0.3; do + git-config --add svn-remote.svn.fetch \ + \$i:refs/remotes/\$i || exit 1; done " # refs should all be different, but the trees should all be the same: From fbcc1737d61f2b83846c59a7cc0d820e09833350 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 6 Feb 2007 18:35:30 -0800 Subject: [PATCH 129/201] git-svn: reintroduce using a single get_log() to fetch We'll need to rely on path matching to handle wildcard support for branches and tags. Signed-off-by: Eric Wong --- git-svn.perl | 103 ++++++++++++++++++++++++--------------------------- 1 file changed, 49 insertions(+), 54 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 6874eabffa..d0bde71f2e 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1112,6 +1112,24 @@ sub do_git_commit { return $commit; } +sub match_paths { + my ($self, $paths, $r) = @_; + $self->{path_regex} ||= qr/^\/\Q$self->{path}\E\/?/; + if (grep /$self->{path_regex}/, keys %$paths) { + return 1; + } + my $c = ''; + foreach (split m#/#, $self->{path}) { + $c .= "/$_"; + next unless ($paths->{$c} && ($paths->{$c}->{action} eq 'A')); + my @x = eval { $self->ra->get_dir($self->{path}, $r) }; + if (scalar @x == 3) { + return 1; + } + } + return 0; +} + sub find_parent_branch { my ($self, $paths, $rev) = @_; return undef unless $_follow_parent; @@ -1308,15 +1326,21 @@ sub make_log_entry { print $un $_, "\n" foreach @$untracked; my %log_entry = ( parents => $parents || [], revision => $rev, log => ''); - my $rp = $self->ra->rev_proplist($rev); - foreach (sort keys %$rp) { - my $v = $rp->{$_}; - if (/^svn:(author|date|log)$/) { - $log_entry{$1} = $v; - } else { - print $un " rev_prop: ", uri_encode($_), ' ', - uri_encode($v), "\n"; + + my $logged = delete $self->{logged_rev_props}; + if (!$logged || $self->{-want_extra_revprops}) { + my $rp = $self->ra->rev_proplist($rev); + foreach (sort keys %$rp) { + my $v = $rp->{$_}; + if (/^svn:(author|date|log)$/) { + $log_entry{$1} = $v; + } else { + print $un " rev_prop: ", uri_encode($_), ' ', + uri_encode($v), "\n"; + } } + } else { + map { $log_entry{$_} = $logged->{$_} } keys %$logged; } close $un or croak $!; @@ -2416,55 +2440,26 @@ sub gs_fetch_loop_common { } while (1) { my %revs; - my $err; my $err_handler = $SVN::Error::handler; - $SVN::Error::handler = sub { - ($err) = @_; - skip_unknown_revs($err); - }; - foreach my $gs (@gs) { - my $min_r = $min; - my $rdb_max = $gs->rev_db_max; - next if $rdb_max >= $max; - $min_r = $rdb_max + 1 if ($rdb_max > $min_r); - $self->get_log([$gs->{path}], $min_r, $max, - 0, 1, 1, sub - { my ($paths, $rev) = @_; - push @{$revs{$rev}}, - [ $gs, - dup_changed_paths($paths) ] }); - - next unless ($err && $max >= $head); - - print STDERR "Path '$gs->{path}' ", - "was probably deleted:\n", - $err->expanded_message, - "\nWill attempt to follow ", - "revisions r$min .. r$max ", - "committed before the deletion\n"; - my $hi = $max; - while (--$hi >= $min) { - my $ok; - $self->get_log([$gs->{path}], $min, $hi, - 0, 1, 1, sub { - my ($paths, $rev) = @_; - $ok = $rev; - push @{$revs{$rev}}, [ $gs, - dup_changed_paths($_[0])]}); - if ($ok) { - print STDERR "r$min .. r$ok OK\n"; - last; - } - } - } + $SVN::Error::handler = \&skip_unknown_revs; + $self->get_log([''], $min, $max, 0, 1, 1, sub { + my ($paths, $r, $author, $date, $log) = @_; + $revs{$r} = [ dup_changed_paths($paths), + { author => $author, + date => $date, + log => $log } ] }); $SVN::Error::handler = $err_handler; + foreach my $r (sort {$a <=> $b} keys %revs) { - foreach (@{$revs{$r}}) { - my ($gs, $paths) = @$_; - my $lr = $gs->last_rev; - next if defined $lr && $lr >= $r; - next if defined $gs->rev_db_get($r); - if (my $log_entry = $gs->do_fetch($paths, $r)) { + my ($paths, $logged) = @{$revs{$r}}; + foreach my $gs (@gs) { + if ($gs->rev_db_max >= $r) { + next; + } + next unless $gs->match_paths($paths, $r); + $gs->{logged_rev_props} = $logged; + my $log_entry = $gs->do_fetch($paths, $r); + if ($log_entry) { $gs->do_git_commit($log_entry); } } From d2ae14346c71d57c57dc7b1c1f8272c09f51ccf9 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 7 Feb 2007 11:50:16 -0800 Subject: [PATCH 130/201] git-svn: run get_log() on a sub-directory if possible This is an optimization that should conserve network bandwidth on certain repositories and configurations. Signed-off-by: Eric Wong --- git-svn.perl | 55 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index d0bde71f2e..5e1a64c6d6 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2433,21 +2433,62 @@ sub gs_fetch_loop_common { my ($self, $base, $head, @gs) = @_; my $inc = 1000; my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc); + + my %common; foreach my $gs (@gs) { if (my $last_commit = $gs->last_commit) { $gs->assert_index_clean($last_commit); } + my @tmp = split m#/#, $gs->{path}; + my $p = ''; + foreach (@tmp) { + $p .= length($p) ? "/$_" : $_; + $common{$p} ||= 0; + $common{$p}++; + } + } + my $longest_path = ''; + foreach (sort {length $b <=> length $a} keys %common) { + if ($common{$_} == @gs) { + $longest_path = $_; + last; + } } while (1) { my %revs; + my $err; my $err_handler = $SVN::Error::handler; - $SVN::Error::handler = \&skip_unknown_revs; - $self->get_log([''], $min, $max, 0, 1, 1, sub { - my ($paths, $r, $author, $date, $log) = @_; - $revs{$r} = [ dup_changed_paths($paths), - { author => $author, - date => $date, - log => $log } ] }); + $SVN::Error::handler = sub { + ($err) = @_; + skip_unknown_revs($err); + }; + sub _cb { + my ($paths, $r, $author, $date, $log) = @_; + [ dup_changed_paths($paths), + { author => $author, date => $date, log => $log } ]; + } + $self->get_log([$longest_path], $min, $max, 0, 1, 1, + sub { $revs{$_[1]} = _cb(@_) }); + if ($err && $max >= $head) { + print STDERR "Path '$longest_path' ", + "was probably deleted:\n", + $err->expanded_message, + "\nWill attempt to follow ", + "revisions r$min .. r$max ", + "committed before the deletion\n"; + my $hi = $max; + while (--$hi >= $min) { + my $ok; + $self->get_log([$longest_path], $min, $hi, + 0, 1, 1, sub { + $ok ||= $_[1]; + $revs{$_[1]} = _cb(@_) }); + if ($ok) { + print STDERR "r$min .. r$ok OK\n"; + last; + } + } + } $SVN::Error::handler = $err_handler; foreach my $r (sort {$a <=> $b} keys %revs) { From e518192f3be92097ba550098dbb69d769ca18429 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 8 Feb 2007 12:53:57 -0800 Subject: [PATCH 131/201] git-svn: implement auto-discovery of branches/tags This is similar to the way git proper handles refs, except we use the keys 'branches' and 'tags' to distinguish when we want to use wildcards. The left-hand side of the ':' contains the remote path, and must have one asterisk ('*') in it for the branch name. The asterisk may be in any component of the path as long as is it on its own directory level. The right-hand side contains the refname and must have the asterisk as the last path component. branches = branches/*:refs/remotes/* tags = tags/*:refs/remotes/tags/* Signed-off-by: Eric Wong --- git-svn.perl | 148 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 120 insertions(+), 28 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 5e1a64c6d6..21d53054f6 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -701,14 +701,32 @@ sub resolve_local_globs { sub fetch_all { my ($repo_id, $remotes) = @_; - my $fetch = $remotes->{$repo_id}->{fetch}; - my $url = $remotes->{$repo_id}->{url}; - my @gs; - resolve_local_globs($url, $fetch, $remotes->{$repo_id}->{branches}); - resolve_local_globs($url, $fetch, $remotes->{$repo_id}->{tags}); + my $remote = $remotes->{$repo_id}; + my $fetch = $remote->{fetch}; + my $url = $remote->{url}; + my (@gs, @globs); my $ra = Git::SVN::Ra->new($url); + my $uuid = $ra->uuid; my $head = $ra->get_latest_revnum; my $base = $head; + + # read the max revs for wildcard expansion (branches/*, tags/*) + foreach my $t (qw/branches tags/) { + defined $remote->{$t} or next; + push @globs, $remote->{$t}; + my $f = "$ENV{GIT_DIR}/svn/.$uuid.$t"; + if (open my $fh, '<', $f) { + chomp(my $max_rev = <$fh>); + close $fh or die "Error closing $f: $!\n"; + + if ($max_rev !~ /^\d+$/) { + die "$max_rev (in $f) is not an integer!\n"; + } + $remote->{$t}->{max_rev} = $max_rev; + $base = $max_rev if ($max_rev < $base); + } + } + foreach my $p (sort keys %$fetch) { my $gs = Git::SVN->new($fetch->{$p}, $repo_id, $p); my $lr = $gs->rev_db_max; @@ -717,8 +735,7 @@ sub fetch_all { } push @gs, $gs; } - return if (++$base > $head); - $ra->gs_fetch_loop_common($base, $head, @gs); + $ra->gs_fetch_loop_common($base, $head, \@gs, \@globs); } sub read_all_remotes { @@ -732,6 +749,7 @@ sub read_all_remotes { (.*):refs/remotes/(.+)\s*$/!x) { my ($p, $g) = ($3, $4); my $rs = $r->{$1}->{$2} = { + t => $2, path => Git::SVN::GlobSpec->new($p), ref => Git::SVN::GlobSpec->new($g) }; if (length($rs->{ref}->{right}) != 0) { @@ -793,20 +811,26 @@ sub init_remote_config { my $r = read_all_remotes(); my $existing = find_existing_remote($url, $r); if ($existing) { - print STDERR "Using existing ", - "[svn-remote \"$existing\"]\n"; + unless ($no_write) { + print STDERR "Using existing ", + "[svn-remote \"$existing\"]\n"; + } $self->{repo_id} = $existing; } else { my $min_url = Git::SVN::Ra->new($url)->minimize_url; $existing = find_existing_remote($min_url, $r); if ($existing) { - print STDERR "Using existing ", - "[svn-remote \"$existing\"]\n"; + unless ($no_write) { + print STDERR "Using existing ", + "[svn-remote \"$existing\"]\n"; + } $self->{repo_id} = $existing; } if ($min_url ne $url) { - print STDERR "Using higher level of URL: ", - "$url => $min_url\n"; + unless ($no_write) { + print STDERR "Using higher level of URL: ", + "$url => $min_url\n"; + } my $old_path = $self->{path}; $self->{path} = $url; $self->{path} =~ s!^\Q$min_url\E/*!!; @@ -1122,8 +1146,8 @@ sub match_paths { foreach (split m#/#, $self->{path}) { $c .= "/$_"; next unless ($paths->{$c} && ($paths->{$c}->{action} eq 'A')); - my @x = eval { $self->ra->get_dir($self->{path}, $r) }; - if (scalar @x == 3) { + if ($self->ra->check_path($self->{path}, $r) == + $SVN::Node::dir) { return 1; } } @@ -1172,6 +1196,10 @@ sub find_parent_branch { my $u = $remotes->{$repo_id}->{url} or next; next if $url ne $u; my $fetch = $remotes->{$repo_id}->{fetch}; + foreach (qw/branches tags/) { + resolve_local_globs($url, $fetch, + $remotes->{$repo_id}->{$_}); + } foreach my $f (keys %$fetch) { next if $f ne $branch_from; $gs = Git::SVN->new($fetch->{$f}, $repo_id, $f); @@ -1238,7 +1266,7 @@ sub do_fetch { my ($self, $paths, $rev) = @_; my $ed; my ($last_rev, @parents); - if ($self->{last_commit}) { + if ($self->last_commit) { $ed = SVN::Git::Fetcher->new($self); $last_rev = $self->{last_rev}; $ed->{c} = $self->{last_commit}; @@ -1354,8 +1382,7 @@ sub fetch { my ($self, $min_rev, $max_rev, @parents) = @_; my ($last_rev, $last_commit) = $self->last_rev_commit; my ($base, $head) = $self->get_fetch_range($min_rev, $max_rev); - return if ($base > $head); - $self->ra->gs_fetch_loop_common($base, $head, $self); + $self->ra->gs_fetch_loop_common($base, $head, [$self]); } sub set_tree_cb { @@ -2430,12 +2457,14 @@ sub gs_do_switch { } sub gs_fetch_loop_common { - my ($self, $base, $head, @gs) = @_; + my ($self, $base, $head, $gsv, $globs) = @_; + return if ($base > $head); my $inc = 1000; my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc); - my %common; - foreach my $gs (@gs) { + my $common_max = scalar @$gsv; + + foreach my $gs (@$gsv) { if (my $last_commit = $gs->last_commit) { $gs->assert_index_clean($last_commit); } @@ -2447,9 +2476,21 @@ sub gs_fetch_loop_common { $common{$p}++; } } + $globs ||= []; + $common_max += scalar @$globs; + foreach my $glob (@$globs) { + my @tmp = split m#/#, $glob->{path}->{left}; + my $p = ''; + foreach (@tmp) { + $p .= length($p) ? "/$_" : $_; + $common{$p} ||= 0; + $common{$p}++; + } + } + my $longest_path = ''; foreach (sort {length $b <=> length $a} keys %common) { - if ($common{$_} == @gs) { + if ($common{$_} == $common_max) { $longest_path = $_; last; } @@ -2491,9 +2532,12 @@ sub gs_fetch_loop_common { } $SVN::Error::handler = $err_handler; + my %exists = map { $_->{path} => $_ } @$gsv; foreach my $r (sort {$a <=> $b} keys %revs) { my ($paths, $logged) = @{$revs{$r}}; - foreach my $gs (@gs) { + + foreach my $gs ($self->match_globs(\%exists, $paths, + $globs, $r)) { if ($gs->rev_db_max >= $r) { next; } @@ -2504,10 +2548,22 @@ sub gs_fetch_loop_common { $gs->do_git_commit($log_entry); } } + foreach my $g (@$globs) { + my $f = "$ENV{GIT_DIR}/svn/." . + $self->uuid . ".$g->{t}"; + open my $fh, '>', "$f.tmp" or + die "Can't open $f.tmp for writing: $!"; + print $fh "$r\n" or + die "Couldn't write to $f: $!\n"; + close $fh or die "Error closing $f: $!\n"; + rename "$f.tmp", $f or + die "Couldn't rename ", + "$f.tmp => $f: $!\n"; + } } # pre-fill the .rev_db since it'll eventually get filled in # with '0' x40 if something new gets committed - foreach my $gs (@gs) { + foreach my $gs (@$gsv) { next if defined $gs->rev_db_get($max); $gs->rev_db_set($max, 0 x40); } @@ -2518,6 +2574,43 @@ sub gs_fetch_loop_common { } } +sub match_globs { + my ($self, $exists, $paths, $globs, $r) = @_; + foreach my $g (@$globs) { + foreach (keys %$paths) { + next unless /$g->{path}->{regex}/; + my $p = $1; + my $pathname = $g->{path}->full_path($p); + next if $exists->{$pathname}; + $exists->{$pathname} = Git::SVN->init( + $self->{url}, $pathname, undef, + $g->{ref}->full_path($p), 1); + } + my $c = ''; + foreach (split m#/#, $g->{path}->{left}) { + $c .= "/$_"; + next unless ($paths->{$c} && + ($paths->{$c}->{action} eq 'A')); + my @x = eval { $self->get_dir($g->{path}->{left}, $r) }; + next unless scalar @x == 3; + my $dirents = $x[0]; + foreach my $de (keys %$dirents) { + next if $dirents->{$de}->kind != + $SVN::Node::dir; + my $p = $g->{path}->full_path($de); + next if $exists->{$p}; + next if (length $g->{path}->{right} && + ($self->check_path($p, $r) != + $SVN::Node::dir)); + $exists->{$p} = Git::SVN->init($self->{url}, + $p, undef, + $g->{ref}->full_path($de), 1); + } + } + } + values %$exists; +} + sub minimize_url { my ($self) = @_; return $self->{url} if ($self->{url} eq $self->{repos_root}); @@ -3167,16 +3260,15 @@ package Git::SVN::GlobSpec; sub new { my ($class, $glob) = @_; - warn "glob: $glob\n"; my $re = $glob; $re =~ s!/+$!!g; # no need for trailing slashes my $nr = ($re =~ s!^(.*/?)\*(/?.*)$!\(\[^/\]+\)!g); my ($left, $right) = ($1, $2); if ($nr > 1) { - warn "Only one '*' wildcard expansion ", - "is supported (got $nr): '$glob'\n"; + die "Only one '*' wildcard expansion ", + "is supported (got $nr): '$glob'\n"; } elsif ($nr == 0) { - warn "One '*' is needed for glob: '$glob'\n"; + die "One '*' is needed for glob: '$glob'\n"; } $re = quotemeta($left) . $re . quotemeta($right); $left =~ s!/+$!!g; From b9dffd8cad737a07d6a05503318c6746ac593f9c Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 9 Feb 2007 01:28:30 -0800 Subject: [PATCH 132/201] git-svn: --follow-parent tracks multi-parent paths We can have a branch that was deleted, then re-added under the same name but copied from another path, in which case we'll have multiple parents (we don't want to break the original ref, nor lose copypath info). Add a test for this, too, of course. Signed-off-by: Eric Wong --- git-svn.perl | 14 +++++++++++--- t/t9104-git-svn-follow-parent.sh | 7 +++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 21d53054f6..cd35efec7e 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1266,11 +1266,19 @@ sub do_fetch { my ($self, $paths, $rev) = @_; my $ed; my ($last_rev, @parents); - if ($self->last_commit) { + if (my $lc = $self->last_commit) { + # we can have a branch that was deleted, then re-added + # under the same name but copied from another path, in + # which case we'll have multiple parents (we don't + # want to break the original ref, nor lose copypath info): + if (my $log_entry = $self->find_parent_branch($paths, $rev)) { + push @{$log_entry->{parents}}, $lc; + return $log_entry; + } $ed = SVN::Git::Fetcher->new($self); $last_rev = $self->{last_rev}; - $ed->{c} = $self->{last_commit}; - @parents = ($self->{last_commit}); + $ed->{c} = $lc; + @parents = ($lc); } else { $last_rev = $rev; if (my $log_entry = $self->find_parent_branch($paths, $rev)) { diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index eebb84974c..f5b7e5efe0 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -146,6 +146,13 @@ test_expect_success "track initial change if it was only made to parent" " \"\`git rev-parse r9270-d~1\`\" " +test_expect_success "track multi-parent paths" " + svn cp -m 'resurrect /glob' $svnrepo/r9270 $svnrepo/glob && + git-svn multi-fetch --follow-parent && + test \`git cat-file commit refs/remotes/glob | \ + grep '^parent ' | wc -l\` -eq 2 + " + test_expect_success "multi-fetch continues to work" " git-svn multi-fetch --follow-parent " From d542aedb9424872474e896a6d20407745d4bd627 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 9 Feb 2007 02:19:41 -0800 Subject: [PATCH 133/201] git-svn: remove check_path calls before calling do_update These checks were needed before git-svn got smarter about match_paths() and using path information returned by get_log(). We also have extra checking against fetching revisions out-of-order these days; so we don't have to worry about that as much. We also check for tree deletions in match_paths() and skip those as well. Signed-off-by: Eric Wong --- git-svn.perl | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index cd35efec7e..819d25e289 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1138,6 +1138,9 @@ sub do_git_commit { sub match_paths { my ($self, $paths, $r) = @_; + if (my $path = $paths->{"/$self->{path}"}) { + return ($path->{action} eq 'D') ? 0 : 1; + } $self->{path_regex} ||= qr/^\/\Q$self->{path}\E\/?/; if (grep /$self->{path_regex}/, keys %$paths) { return 1; @@ -2394,14 +2397,6 @@ sub gs_do_update { my $new = ($rev_a == $rev_b); my $path = $gs->{path}; - my $ta = $self->check_path($path, $rev_a); - my $tb = $new ? $ta : $self->check_path($path, $rev_b); - return 1 if ($tb != $SVN::Node::dir && $ta != $SVN::Node::dir); - if ($ta == $SVN::Node::none) { - $rev_a = $rev_b; - $new = 1; - } - my $pool = SVN::Pool->new; $editor->set_path_strip($path); my (@pc) = split m#/#, $path; From e20bea654513aa7eba7e356994fa56cc90bb5b6d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 9 Feb 2007 02:32:48 -0800 Subject: [PATCH 134/201] git-svn: remove some noisy debugging messages We don't need them anymore, all the rough points of the --follow-parent implementation have been worked out. The only improvement in the future will probably be --follow-parent-harder, which will track subdirectories and follow individual file history (so annotate/blame can be complete); but that is still a ways off. Signed-off-by: Eric Wong --- git-svn.perl | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 819d25e289..c8a1df3651 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -692,8 +692,6 @@ sub resolve_local_globs { next; } } else { - warn "Globbed ($path->{glob}:$ref->{glob}): ", - "$pathname == $refname\n"; $fetch->{$pathname} = $refname; } } @@ -1179,8 +1177,8 @@ sub find_parent_branch { last if $i; unshift(@a_path_components, pop(@b_path_components)); } - goto not_found unless defined $i; - my $branch_from = $i->{copyfrom_path} or goto not_found; + return undef unless defined $i; + my $branch_from = $i->{copyfrom_path} or return undef; if (@a_path_components) { print STDERR "branch_from: $branch_from => "; $branch_from .= '/'.join('/', @a_path_components); @@ -1247,21 +1245,6 @@ sub find_parent_branch { print STDERR "Successfully followed parent\n"; return $self->make_log_entry($rev, [$parent], $ed); } -not_found: - print STDERR "Branch parent for path: '/", - $self->rel_path, "' @ r$rev not found:\n"; - return undef unless $paths; - print STDERR "Changed paths:\n"; - foreach my $x (sort keys %$paths) { - my $p = $paths->{$x}; - print STDERR "\t$p->{action}\t$x"; - if ($p->{copyfrom_path}) { - print STDERR "(from $p->{copyfrom_path}: ", - "$p->{copyfrom_rev})"; - } - print STDERR "\n"; - } - print STDERR '-'x72, "\n"; return undef; } From 0bed5eaa0edc3a2dfa0d5910ff1fb280539b242d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 9 Feb 2007 02:45:03 -0800 Subject: [PATCH 135/201] git-svn: enable follow-parent functionality by default --no-follow-parent disables and reverts it back to the old default behavior of not following parents (if you don't care for full history). Signed-off-by: Eric Wong --- Documentation/git-svn.txt | 3 ++- git-svn.perl | 4 ++-- t/t9104-git-svn-follow-parent.sh | 26 +++++++++++++------------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 2914042587..50dc6ac818 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -311,7 +311,8 @@ for more information on using GIT_SVN_ID. This is especially helpful when we're tracking a directory that has been moved around within the repository, or if we started tracking a branch and never tracked the trunk it was - descended from. + descended from. This feature is enabled by default, use + --no-follow-parent to disable it. config key: svn.followparent diff --git a/git-svn.perl b/git-svn.perl index c8a1df3651..b9bacc384e 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -57,11 +57,11 @@ BEGIN $_version, $_merge, $_strategy, $_dry_run, $_prefix); - +$Git::SVN::_follow_parent = 1; my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username, 'config-dir=s' => \$Git::SVN::Ra::config_dir, 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache ); -my %fc_opts = ( 'follow-parent|follow' => \$Git::SVN::_follow_parent, +my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent, 'authors-file|A=s' => \$_authors, 'repack:i' => \$Git::SVN::_repack, 'no-metadata' => \$Git::SVN::_no_metadata, diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index f5b7e5efe0..53f5a925ac 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -3,7 +3,7 @@ # Copyright (c) 2006 Eric Wong # -test_description='git-svn --follow-parent fetching' +test_description='git-svn fetching' . ./lib-git-svn.sh test_expect_success 'initialize repo' " @@ -27,9 +27,9 @@ test_expect_success 'initialize repo' " cd .. " -test_expect_success 'init and fetch --follow-parent a moved directory' " +test_expect_success 'init and fetch a moved directory' " git-svn init -i thunk $svnrepo/thunk && - git-svn fetch --follow-parent -i thunk && + git-svn fetch -i thunk && test \"\`git-rev-parse --verify refs/remotes/thunk@2\`\" \ = \"\`git-rev-parse --verify refs/remotes/thunk~1\`\" && test \"\`git-cat-file blob refs/remotes/thunk:readme |\ @@ -44,7 +44,7 @@ test_expect_success 'init and fetch from one svn-remote' " trunk:refs/remotes/svn/trunk && git-repo-config --add svn-remote.svn.fetch \ thunk:refs/remotes/svn/thunk && - git-svn fetch --follow-parent -i svn/thunk && + git-svn fetch -i svn/thunk && test \"\`git-rev-parse --verify refs/remotes/svn/trunk\`\" \ = \"\`git-rev-parse --verify refs/remotes/svn/thunk~1\`\" && test \"\`git-cat-file blob refs/remotes/svn/thunk:readme |\ @@ -56,8 +56,8 @@ test_expect_success 'follow deleted parent' " -r2 $svnrepo/trunk $svnrepo/junk && git-repo-config --add svn-remote.svn.fetch \ junk:refs/remotes/svn/junk && - git-svn fetch --follow-parent -i svn/thunk && - git-svn fetch -i svn/junk --follow-parent && + git-svn fetch -i svn/thunk && + git-svn fetch -i svn/junk && test -z \"\`git diff svn/junk svn/trunk\`\" && test \"\`git merge-base svn/junk svn/trunk\`\" \ = \"\`git rev-parse svn/trunk\`\" @@ -69,7 +69,7 @@ test_expect_success 'follow larger parent' " svn import -m 'import a larger parent' import $svnrepo/larger-parent && svn cp -m 'hi' $svnrepo/larger-parent $svnrepo/another-larger && git-svn init -i larger $svnrepo/another-larger/trunk/thunk/bump/thud && - git-svn fetch -i larger --follow-parent && + git-svn fetch -i larger && git-rev-parse --verify refs/remotes/larger && git-rev-parse --verify \ refs/remotes/larger-parent/trunk/thunk/bump/thud && @@ -91,7 +91,7 @@ test_expect_success 'follow higher-level parent' " svn mkdir -m 'new glob at top level' $svnrepo/glob && svn mv -m 'move blob down a level' $svnrepo/blob $svnrepo/glob/blob && git-svn init -i blob $svnrepo/glob/blob && - git-svn fetch -i blob --follow-parent + git-svn fetch -i blob " test_expect_success 'follow deleted directory' " @@ -128,7 +128,7 @@ test_expect_success 'follow-parent avoids deleting relevant info' " cd .. && git-svn init -i r9270-t \ $svnrepo/r9270/trunk/subversion/bindings/swig/perl/native/t && - git-svn fetch -i r9270-t --follow-parent && + git-svn fetch -i r9270-t && test \`git rev-list r9270-t | wc -l\` -eq 2 && test \"\`git ls-tree --name-only r9270-t~1\`\" = \ \"\`git ls-tree --name-only r9270-t\`\" @@ -138,7 +138,7 @@ test_expect_success "track initial change if it was only made to parent" " svn cp -m 'wheee!' $svnrepo/r9270/trunk $svnrepo/r9270/drunk && git-svn init -i r9270-d \ $svnrepo/r9270/drunk/subversion/bindings/swig/perl/native/t && - git-svn fetch -i r9270-d --follow-parent && + git-svn fetch -i r9270-d && test \`git rev-list r9270-d | wc -l\` -eq 3 && test \"\`git ls-tree --name-only r9270-t\`\" = \ \"\`git ls-tree --name-only r9270-d\`\" && @@ -148,19 +148,19 @@ test_expect_success "track initial change if it was only made to parent" " test_expect_success "track multi-parent paths" " svn cp -m 'resurrect /glob' $svnrepo/r9270 $svnrepo/glob && - git-svn multi-fetch --follow-parent && + git-svn multi-fetch && test \`git cat-file commit refs/remotes/glob | \ grep '^parent ' | wc -l\` -eq 2 " test_expect_success "multi-fetch continues to work" " - git-svn multi-fetch --follow-parent + git-svn multi-fetch " test_expect_success "multi-fetch works off a 'clean' repository" " rm -r $GIT_DIR/svn $GIT_DIR/refs/remotes $GIT_DIR/logs && mkdir $GIT_DIR/svn && - git-svn multi-fetch --follow-parent + git-svn multi-fetch " test_debug 'gitk --all &' From 4e9f6cc78e5d955bd0faffe76ae9aea6590189f1 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 9 Feb 2007 12:17:57 -0800 Subject: [PATCH 136/201] git-svn: fix buggy regular expression usage in several places I incorrectly used $path/? and $path/* to strip off leading directories, but places where $path = 'branches/0.17' would incorrectly strip changes to 'branches/0.17.1' as well. For globs, we require that our '*' is its own path component (surrounded by '/' or nothing). Enforce this when --prefix= is passed to us, too. Signed-off-by: Eric Wong --- git-svn.perl | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index b9bacc384e..7664b385f6 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -492,6 +492,9 @@ sub complete_url_ls_init { $remote_path =~ s#/+#/#g; $remote_path =~ s#^/##g; my ($n) = ($switch =~ /^--(\w+)/); + if (length $pfx && $pfx !~ m#/$#) { + die "--prefix='$pfx' must have a trailing slash '/'\n"; + } command_noisy('config', "svn-remote.$remote_id.$n", "$remote_path:refs/remotes/$pfx*"); } @@ -681,7 +684,7 @@ sub resolve_local_globs { " globbed: refs/remotes/$refname\n"; } my $u = (::cmt_metadata("refs/remotes/$refname"))[0]; - $u =~ s!^\Q$url\E/?!! or die + $u =~ s!^\Q$url\E(/|$)!! or die "refs/remotes/$refname: '$url' not found in '$u'\n"; if ($pathname ne $u) { warn "W: Refspec glob conflict ", @@ -831,7 +834,7 @@ sub init_remote_config { } my $old_path = $self->{path}; $self->{path} = $url; - $self->{path} =~ s!^\Q$min_url\E/*!!; + $self->{path} =~ s!^\Q$min_url\E(/|$)!!; if (length $old_path) { $self->{path} .= "/$old_path"; } @@ -927,10 +930,8 @@ sub rel_path { my ($self) = @_; my $repos_root = $self->ra->{repos_root}; return $self->{path} if ($self->{url} eq $repos_root); - my $url = $self->{url} . - (length $self->{path} ? "/$self->{path}" : $self->{path}); - $url =~ s!^\Q$repos_root\E/*!!g; - $url; + die "BUG: rel_path failed! repos_root: $repos_root, Ra URL: ", + $self->ra->{url}, " path: $self->{path}, URL: $self->{url}\n"; } sub traverse_ignore { @@ -1136,10 +1137,11 @@ sub do_git_commit { sub match_paths { my ($self, $paths, $r) = @_; + return 1 if $self->{path} eq ''; if (my $path = $paths->{"/$self->{path}"}) { return ($path->{action} eq 'D') ? 0 : 1; } - $self->{path_regex} ||= qr/^\/\Q$self->{path}\E\/?/; + $self->{path_regex} ||= qr/^\/\Q$self->{path}\E\//; if (grep /$self->{path_regex}/, keys %$paths) { return 1; } @@ -1732,7 +1734,7 @@ sub new { sub set_path_strip { my ($self, $path) = @_; - $self->{path_strip} = qr/^\Q$path\E\/?/; + $self->{path_strip} = qr/^\Q$path\E(\/|$)/ if length $path; } sub open_root { @@ -2347,7 +2349,7 @@ sub new { auth_provider_callbacks => $callbacks); $self->{svn_path} = $url; $self->{repos_root} = $self->get_repos_root; - $self->{svn_path} =~ s#^\Q$self->{repos_root}\E/*##; + $self->{svn_path} =~ s#^\Q$self->{repos_root}\E(/|$)##; $RA = bless $self, $class; } @@ -3136,7 +3138,7 @@ sub minimize_connections { my $root_ra = Git::SVN::Ra->new($ra->{repos_root}); my $root_path = $ra->{url}; - $root_path =~ s#^\Q$ra->{repos_root}\E/*##; + $root_path =~ s#^\Q$ra->{repos_root}\E(/|$)##; foreach my $path (keys %$fetch) { my $ref_id = $fetch->{$path}; my $gs = Git::SVN->new($ref_id, $repo_id, $path); @@ -3248,7 +3250,7 @@ sub new { my ($class, $glob) = @_; my $re = $glob; $re =~ s!/+$!!g; # no need for trailing slashes - my $nr = ($re =~ s!^(.*/?)\*(/?.*)$!\(\[^/\]+\)!g); + my $nr = ($re =~ s!^(.*)\*(.*)$!\(\[^/\]+\)!g); my ($left, $right) = ($1, $2); if ($nr > 1) { die "Only one '*' wildcard expansion ", @@ -3257,8 +3259,12 @@ sub new { die "One '*' is needed for glob: '$glob'\n"; } $re = quotemeta($left) . $re . quotemeta($right); - $left =~ s!/+$!!g; - $right =~ s!^/+!!g; + if (length $left && !($left =~ s!/+$!!g)) { + die "Missing trailing '/' on left side of: '$glob' ($left)\n"; + } + if (length $right && !($right =~ s!^/+!!g)) { + die "Missing leading '/' on right side of: '$glob' ($right)\n"; + } bless { left => $left, right => $right, regex => qr/$re/, glob => $glob }, $class; } From 9e3cdbd4f2e02bf63bfaa8f6e2747601f117cf2d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 9 Feb 2007 12:23:47 -0800 Subject: [PATCH 137/201] git-svn: correctly handle the -q flag in SVN::Git::Fetcher Signed-off-by: Eric Wong --- git-svn.perl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 7664b385f6..ed363e972d 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1770,14 +1770,14 @@ sub delete_entry { while (<$ls>) { chomp; $self->{gii}->remove($_); - print "\tD\t$_\n" unless $self->{q}; + print "\tD\t$_\n" unless $::_q; } - print "\tD\t$gpath/\n" unless $self->{q}; + print "\tD\t$gpath/\n" unless $::_q; command_close_pipe($ls, $ctx); $self->{empty}->{$path} = 0 } else { $self->{gii}->remove($gpath); - print "\tD\t$gpath\n" unless $self->{q}; + print "\tD\t$gpath\n" unless $::_q; } undef; } @@ -1913,7 +1913,7 @@ sub close_file { } $fb->{pool}->clear; $self->{gii}->update($fb->{mode_b}, $hash, $path) or croak $!; - print "\t$fb->{action}\t$path\n" if $fb->{action} && ! $self->{q}; + print "\t$fb->{action}\t$path\n" if $fb->{action} && ! $::_q; undef; } From 74a81227f95b52b1c3f7ac7ba84ac1a6e1708995 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 10 Feb 2007 13:28:50 -0800 Subject: [PATCH 138/201] git-svn: correctly handle globs with a right-hand-side path component Several bugs were found and fixed while getting this to work: * Remember the 'R'(eplace) case of actions and treat it like we would an 'A'(dd) case. * Fix a small case of follow-parent missing a parent if a subdirectory was modified in the revision where the parent was copied. * dirents returned by get_dir sometimes expire if the data structure is too big and the pool is destroyed, so we cache get_dir (along with check_path and get_revprops) temporarily along with its pool. Signed-off-by: Eric Wong --- git-svn.perl | 84 +++++++++++++++++++++++++++++------------ t/t9108-git-svn-glob.sh | 53 ++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 24 deletions(-) create mode 100755 t/t9108-git-svn-glob.sh diff --git a/git-svn.perl b/git-svn.perl index ed363e972d..50b7dcf255 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1148,7 +1148,8 @@ sub match_paths { my $c = ''; foreach (split m#/#, $self->{path}) { $c .= "/$_"; - next unless ($paths->{$c} && ($paths->{$c}->{action} eq 'A')); + next unless ($paths->{$c} && + ($paths->{$c}->{action} =~ /^[AR]$/)); if ($self->ra->check_path($self->{path}, $r) == $SVN::Node::dir) { return 1; @@ -1176,11 +1177,11 @@ sub find_parent_branch { my $i; while (@b_path_components) { $i = $paths->{'/'.join('/', @b_path_components)}; - last if $i; + last if $i && defined $i->{copyfrom_path}; unshift(@a_path_components, pop(@b_path_components)); } - return undef unless defined $i; - my $branch_from = $i->{copyfrom_path} or return undef; + return undef unless defined $i && defined $i->{copyfrom_path}; + my $branch_from = $i->{copyfrom_path}; if (@a_path_components) { print STDERR "branch_from: $branch_from => "; $branch_from .= '/'.join('/', @a_path_components); @@ -2309,8 +2310,7 @@ package Git::SVN::Ra; BEGIN { # enforce temporary pool usage for some simple functions my $e; - foreach (qw/get_latest_revnum rev_proplist get_file - check_path get_dir get_uuid get_repos_root/) { + foreach (qw/get_latest_revnum get_uuid get_repos_root/) { $e .= "sub $_ { my \$self = shift; my \$pool = SVN::Pool->new; @@ -2318,7 +2318,30 @@ BEGIN \$pool->clear; wantarray ? \@ret : \$ret[0]; }\n"; } - eval $e; + + # get_dir needs $pool held in cache for dirents to work, + # check_path is cacheable and rev_proplist is close enough + # for our purposes. + foreach (qw/check_path get_dir rev_proplist/) { + $e .= "my \%${_}_cache; my \$${_}_rev = 0; sub $_ { + my \$self = shift; + my \$r = pop; + my \$k = join(\"\\0\", \@_); + if (my \$x = \$${_}_cache{\$r}->{\$k}) { + return wantarray ? \@\$x : \$x->[0]; + } + my \$pool = SVN::Pool->new; + my \@ret = \$self->SUPER::$_(\@_, \$r, \$pool); + if (\$r != \$${_}_rev) { + \%${_}_cache = ( pool => [] ); + \$${_}_rev = \$r; + } + \$${_}_cache{\$r}->{\$k} = \\\@ret; + push \@{\$${_}_cache{pool}}, \$pool; + wantarray ? \@ret : \$ret[0]; }\n"; + } + $e .= "\n1;"; + eval $e or die $@; } sub new { @@ -2564,8 +2587,34 @@ sub gs_fetch_loop_common { sub match_globs { my ($self, $exists, $paths, $globs, $r) = @_; + + sub get_dir_check { + my ($self, $exists, $g, $r) = @_; + my @x = eval { $self->get_dir($g->{path}->{left}, $r) }; + return unless scalar @x == 3; + my $dirents = $x[0]; + foreach my $de (keys %$dirents) { + next if $dirents->{$de}->kind != $SVN::Node::dir; + my $p = $g->{path}->full_path($de); + next if $exists->{$p}; + next if (length $g->{path}->{right} && + ($self->check_path($p, $r) != + $SVN::Node::dir)); + $exists->{$p} = Git::SVN->init($self->{url}, $p, undef, + $g->{ref}->full_path($de), 1); + } + } foreach my $g (@$globs) { + if (my $path = $paths->{"/$g->{path}->{left}"}) { + if ($path->{action} =~ /^[AR]$/) { + get_dir_check($self, $exists, $g, $r); + } + } foreach (keys %$paths) { + if (/$g->{path}->{left_regex}/) { + next if $paths->{$_}->{action} !~ /^[AR]$/; + get_dir_check($self, $exists, $g, $r); + } next unless /$g->{path}->{regex}/; my $p = $1; my $pathname = $g->{path}->full_path($p); @@ -2578,22 +2627,8 @@ sub match_globs { foreach (split m#/#, $g->{path}->{left}) { $c .= "/$_"; next unless ($paths->{$c} && - ($paths->{$c}->{action} eq 'A')); - my @x = eval { $self->get_dir($g->{path}->{left}, $r) }; - next unless scalar @x == 3; - my $dirents = $x[0]; - foreach my $de (keys %$dirents) { - next if $dirents->{$de}->kind != - $SVN::Node::dir; - my $p = $g->{path}->full_path($de); - next if $exists->{$p}; - next if (length $g->{path}->{right} && - ($self->check_path($p, $r) != - $SVN::Node::dir)); - $exists->{$p} = Git::SVN->init($self->{url}, - $p, undef, - $g->{ref}->full_path($de), 1); - } + ($paths->{$c}->{action} =~ /^[AR]$/)); + get_dir_check($self, $exists, $g, $r); } } values %$exists; @@ -3265,7 +3300,8 @@ sub new { if (length $right && !($right =~ s!^/+!!g)) { die "Missing leading '/' on right side of: '$glob' ($right)\n"; } - bless { left => $left, right => $right, + my $left_re = qr/^\/\Q$left\E(\/|$)/; + bless { left => $left, right => $right, left_regex => $left_re, regex => qr/$re/, glob => $glob }, $class; } diff --git a/t/t9108-git-svn-glob.sh b/t/t9108-git-svn-glob.sh new file mode 100755 index 0000000000..47cccdfd0e --- /dev/null +++ b/t/t9108-git-svn-glob.sh @@ -0,0 +1,53 @@ +#!/bin/sh +# Copyright (c) 2007 Eric Wong +test_description='git-svn globbing refspecs' +. ./lib-git-svn.sh + +cat > expect.end < trunk/src/a/readme && + echo 'goodbye world' > trunk/src/b/readme && + svn import -m 'initial' trunk $svnrepo/trunk && + svn co $svnrepo tmp && + cd tmp && + mkdir branches tags && + svn add branches tags && + svn cp trunk branches/start && + svn commit -m 'start a new branch' && + svn up && + echo 'hi' >> branches/start/src/b/readme && + echo 'hey' >> branches/start/src/a/readme && + svn commit -m 'hi' && + svn up && + svn cp branches/start tags/end && + echo 'bye' >> tags/end/src/b/readme && + echo 'aye' >> tags/end/src/a/readme && + svn commit -m 'the end' && + echo 'byebye' >> tags/end/src/b/readme && + svn commit -m 'nothing to see here' + cd .. && + git config --add svn-remote.svn.url $svnrepo && + git config --add svn-remote.svn.fetch \ + 'trunk/src/a:refs/remotes/trunk' && + git config --add svn-remote.svn.branches \ + 'branches/*/src/a:refs/remotes/branches/*' && + git config --add svn-remote.svn.tags\ + 'tags/*/src/a:refs/remotes/tags/*' && + git-svn multi-fetch && + git log --pretty=oneline refs/remotes/tags/end | \ + sed -e 's/^.\{41\}//' > output.end && + cmp expect.end output.end && + test \"\`git rev-parse refs/remotes/tags/end~1\`\" = \ + \"\`git rev-parse refs/remotes/branches/start\`\" && + test \"\`git rev-parse refs/remotes/branches/start~2\`\" = \ + \"\`git rev-parse refs/remotes/trunk\`\" + " + +test_done From 490f49ea5899b7aacfb82c0ed5639d722a56704a Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 10 Feb 2007 13:58:33 -0800 Subject: [PATCH 139/201] git-svn: remove optimized commit stuff for set-tree I may resurrect it for dcommit at some point, but nobody really uses set-tree anymore and I don't feel like introducing more complexity into the code at this point. Signed-off-by: Eric Wong --- git-svn.perl | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 50b7dcf255..7e1a655259 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -48,7 +48,6 @@ BEGIN my ($SVN); -my $_optimize_commits = 1 unless $ENV{GIT_SVN_NO_OPTIMIZE_COMMITS}; $sha1 = qr/[a-f\d]{40}/; $sha1_short = qr/[a-f\d]{4,40}/; my ($_stdin, $_help, $_edit, @@ -1384,15 +1383,8 @@ sub fetch { sub set_tree_cb { my ($self, $log_entry, $tree, $rev, $date, $author) = @_; - # TODO: enable and test optimized commits: - if (0 && $rev == ($self->{last_rev} + 1)) { - $log_entry->{revision} = $rev; - $log_entry->{author} = $author; - $self->do_git_commit($log_entry, "$rev=$tree"); - } else { - $self->{inject_parents} = { $rev => $tree }; - $self->fetch(undef, undef); - } + $self->{inject_parents} = { $rev => $tree }; + $self->fetch(undef, undef); } sub set_tree { From 8a49ee9759f72ba7c61e035a2ca4b10d8a51c94e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 10 Feb 2007 20:46:50 -0800 Subject: [PATCH 140/201] git-svn: add support for SVN::Mirror/svk using revprops for metadata Pass --use-svm-props or set the svn.usesvmprops key with git-config to enable using properties set by SVN::Mirror when it mirrored the upstream URL. This is heavily based on work from Sam Vilain: > From: Sam Vilain > Date: Sun, 11 Feb 2007 12:34:45 +1300 > Subject: [PATCH] git-svn: re-map repository URLs and UUIDs on SVK mirror paths > > If an SVN revision has a property, "svm:headrev", it is likely that > the revision was created by SVN::Mirror (a part of SVK). The property > contains a repository UUID and a revision. We want to make it look > like we are mirroring the original URL, so introduce a helper function > that returns the original identity URL and UUID, and use it when > generating commit messages. Signed-off-by: Eric Wong --- git-svn.perl | 115 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 105 insertions(+), 10 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 7e1a655259..23e1d42cf7 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -64,6 +64,7 @@ BEGIN 'authors-file|A=s' => \$_authors, 'repack:i' => \$Git::SVN::_repack, 'no-metadata' => \$Git::SVN::_no_metadata, + 'use-svm-props|svm-props' => \$Git::SVN::_use_svm_props, 'quiet|q' => \$_q, 'repack-flags|repack-args|repack-opts=s' => \$Git::SVN::_repack_flags, @@ -645,7 +646,7 @@ package Git::SVN; use strict; use warnings; use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent - $_repack $_repack_flags/; + $_repack $_repack_flags $_use_svm_props/; use Carp qw/croak/; use File::Path qw/mkpath/; use File::Copy qw/copy/; @@ -920,9 +921,54 @@ sub new { sub refname { "refs/remotes/$_[0]->{ref_id}" } +sub set_svm_vars { + my ($self, $ra) = @_; + my $section = "svn-remote.$self->{repo_id}"; + + # see if we have it in our config, first: + eval { + $self->{svm} = { + source => $self->tmp_config('--get', "$section.svm-source"), + uuid => $self->tmp_config('--get', "$section.svm-uuid"), + } + }; + return $ra if ($self->{svm}->{source} && $self->{svm}->{uuid}); + + # nope, make sure we're connected to the repository root: + if ($ra->{repos_root} ne $self->{url}) { + $ra = Git::SVN::Ra->new($ra->{repos_root}); + } + my $r = $ra->get_latest_revnum; + my ($props) = ($ra->get_dir('', $r))[2]; + if (my $src = $props->{'svm:source'}) { + # don't know what a '!' is there for, also the + # username is of no interest + $src =~ s{!$}{}; + $src =~ s{(^[a-z\+]*://)[^/@]*@}{$1}; + $self->tmp_config('--add', "$section.svm-source", $src); + + my $uuid = $props->{'svm:uuid'}; + $uuid =~ m{^[0-9a-f\-]{30,}$} + or die "doesn't look right - svm:uuid is '$uuid'\n"; + $self->tmp_config('--add', "$section.svm-uuid", $uuid); + + $self->{svm} = { source => $src , uuid => $uuid }; + } + if ($ra->{repos_root} ne $self->{url}) { + $ra = Git::SVN::Ra->new($self->{url}); + } + $ra; +} + sub ra { my ($self) = shift; - Git::SVN::Ra->new($self->{url}); + my $ra = Git::SVN::Ra->new($self->{url}); + $self->{-use_svm_props} = $Git::SVN::_use_svm_props; + if ($self->{-use_svm_props} && !$self->{svm}) { + $ra = $self->set_svm_vars($ra); + $self->{-want_revprops} = 1; + } + $ra; } sub rel_path { @@ -1006,16 +1052,44 @@ sub get_fetch_range { (++$min, $max); } +sub tmp_config { + my ($self, @args) = @_; + unless (-f $self->{config}) { + open my $fh, '>', $self->{config} or + die "Can't open $self->{config}: $!\n"; + print $fh "; This file is used internally by git-svn\n" or + die "Couldn't write to $self->{config}: $!\n"; + print $fh "; You should not have to edit it\n" or + die "Couldn't write to $self->{config}: $!\n"; + close $fh or die "Couldn't close $self->{config}: $!\n"; + } + my $old_config = $ENV{GIT_CONFIG}; + $ENV{GIT_CONFIG} = $self->{config}; + $@ = undef; + my @ret = eval { command('config', @args) }; + my $err = $@; + if (defined $old_config) { + $ENV{GIT_CONFIG} = $old_config; + } else { + delete $ENV{GIT_CONFIG}; + } + die $err if $err; + wantarray ? @ret : $ret[0]; +} + sub tmp_index_do { my ($self, $sub) = @_; my $old_index = $ENV{GIT_INDEX_FILE}; $ENV{GIT_INDEX_FILE} = $self->{index}; - my @ret = &$sub; - if ($old_index) { + $@ = undef; + my @ret = eval { &$sub }; + my $err = $@; + if (defined $old_index) { $ENV{GIT_INDEX_FILE} = $old_index; } else { delete $ENV{GIT_INDEX_FILE}; } + die $err if $err; wantarray ? @ret : $ret[0]; } @@ -1105,9 +1179,8 @@ sub do_git_commit { or croak $!; print $msg_fh $log_entry->{log} or croak $!; unless ($_no_metadata) { - print $msg_fh "\ngit-svn-id: ", $self->full_url, '@', - $log_entry->{revision}, ' ', - $self->ra->uuid, "\n" or croak $!; + print $msg_fh "\ngit-svn-id: $log_entry->{metadata}\n" + or croak $!; } $msg_fh->flush == 0 or croak $!; close $msg_fh or croak $!; @@ -1123,7 +1196,11 @@ sub do_git_commit { $self->{last_rev} = $log_entry->{revision}; $self->{last_commit} = $commit; - print "r$log_entry->{revision} = $commit ($self->{ref_id})\n"; + print "r$log_entry->{revision}"; + if (defined $log_entry->{svm_revision}) { + print " (\@$log_entry->{svm_revision})"; + } + print " = $commit ($self->{ref_id})\n"; if (defined $_repack && (--$_repack_nr == 0)) { $_repack_nr = $_repack; # repack doesn't use any arguments with spaces in them, does it? @@ -1351,13 +1428,16 @@ sub make_log_entry { my %log_entry = ( parents => $parents || [], revision => $rev, log => ''); + my $headrev; my $logged = delete $self->{logged_rev_props}; - if (!$logged || $self->{-want_extra_revprops}) { + if (!$logged || $self->{-want_revprops}) { my $rp = $self->ra->rev_proplist($rev); foreach (sort keys %$rp) { my $v = $rp->{$_}; if (/^svn:(author|date|log)$/) { $log_entry{$1} = $v; + } elsif ($_ eq 'svm:headrev') { + $headrev = $v; } else { print $un " rev_prop: ", uri_encode($_), ' ', uri_encode($v), "\n"; @@ -1371,6 +1451,21 @@ sub make_log_entry { $log_entry{date} = parse_svn_date($log_entry{date}); $log_entry{author} = check_author($log_entry{author}); $log_entry{log} .= "\n"; + if (defined $headrev && $self->{-use_svm_props}) { + my ($uuid, $r) = $headrev =~ m{^([a-f\d\-]{30,}):(\d+)$}; + if ($uuid ne $self->{svm}->{uuid}) { + die "UUID mismatch on SVM path:\n", + "expected: $self->{svm}->{uuid}\n", + " got: $uuid\n"; + } + my $full_url = $self->{svm}->{source}; + $full_url .= "/$self->{path}" if length $self->{path}; + $log_entry{metadata} = "$full_url\@$r $uuid"; + $log_entry{svm_revision} = $r; + } else { + $log_entry{metadata} = $self->full_url . "\@$rev " . + $self->ra->uuid; + } \%log_entry; } @@ -1549,7 +1644,7 @@ sub _new { close $fh or croak $!; } bless { ref_id => $ref_id, dir => $dir, index => "$dir/index", - path => $path, + path => $path, config => "$ENV{GIT_DIR}/svn/config", db_path => "$dir/.rev_db", repo_id => $repo_id }, $class; } From 91b03282b586cc521f9b009ea8273444fb58f2f1 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 11 Feb 2007 00:51:33 -0800 Subject: [PATCH 141/201] git-svn: add support for per-[svn-remote "..."] options Available options are currently: svn-remote..{noMetadata,useSvmProps,followParent} These boolean switches will override options set globally in [svn], and even override options set on the command-line (this should probably change in the future, however). Note that the noMetadata and useSvmProps options conflict. It's both technically and logically impossible to use them together. Signed-off-by: Eric Wong --- git-svn.perl | 44 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 23e1d42cf7..becf2e0d89 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -662,6 +662,29 @@ BEGIN svn:entry:last-author svn:entry:uuid svn:entry:committed-date/; + + # some options are read globally, but can be overridden locally + # per [svn-remote "..."] section. Command-line options will *NOT* + # override options set in an [svn-remote "..."] section + my $e; + foreach (qw/follow_parent no_metadata use_svm_props/) { + my $key = $_; + $key =~ tr/_//d; + $e .= "sub $_ { + my (\$self) = \@_; + return \$self->{-$_} if exists \$self->{-$_}; + my \$k = \"svn-remote.\$self->{repo_id}\.$key\"; + eval { command_oneline(qw/config --get/, \$k) }; + if (\$@) { + \$self->{-$_} = \$Git::SVN::_$_; + } else { + my \$v = command_oneline(qw/config --bool/,\$k); + \$self->{-$_} = \$v eq 'false' ? 0 : 1; + } + return \$self->{-$_} }\n"; + } + $e .= "1;\n"; + eval $e or die $@; } my %LOCKFILES; @@ -963,8 +986,11 @@ sub set_svm_vars { sub ra { my ($self) = shift; my $ra = Git::SVN::Ra->new($self->{url}); - $self->{-use_svm_props} = $Git::SVN::_use_svm_props; - if ($self->{-use_svm_props} && !$self->{svm}) { + if ($self->use_svm_props && !$self->{svm}) { + if ($self->no_metadata) { + die "Can't have both --no-metadata and ", + "--use-svm-props options set!\n"; + } $ra = $self->set_svm_vars($ra); $self->{-want_revprops} = 1; } @@ -1014,7 +1040,7 @@ sub last_rev_commit { return ($self->{last_rev}, $self->{last_commit}); } my $c = ::verify_ref($self->refname.'^0'); - if ($c) { + if ($c && !$self->use_svm_props && !$self->no_metadata) { my $rev = (::cmt_metadata($c))[1]; if (defined $rev) { ($self->{last_rev}, $self->{last_commit}) = ($rev, $c); @@ -1034,7 +1060,7 @@ sub last_rev_commit { sysread($fh, $rl, 41) == 41 or return (undef, undef); chomp $rl; } - if ($c) { + if ($c && $c ne $rl) { die "$self->{db_path} and ", $self->refname, " inconsistent!:\n$c != $rl\n"; } @@ -1178,7 +1204,7 @@ sub do_git_commit { defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec)) or croak $!; print $msg_fh $log_entry->{log} or croak $!; - unless ($_no_metadata) { + unless ($self->no_metadata) { print $msg_fh "\ngit-svn-id: $log_entry->{metadata}\n" or croak $!; } @@ -1236,7 +1262,7 @@ sub match_paths { sub find_parent_branch { my ($self, $paths, $rev) = @_; - return undef unless $_follow_parent; + return undef unless $self->follow_parent; unless (defined $paths) { my $err_handler = $SVN::Error::handler; $SVN::Error::handler = \&Git::SVN::Ra::skip_unknown_revs; @@ -1297,7 +1323,7 @@ sub find_parent_branch { $gs = Git::SVN->init($new_url, '', $ref_id, $ref_id, 1); } my ($r0, $parent) = $gs->find_rev_before($r, 1); - if ($_follow_parent && (!defined $r0 || !defined $parent)) { + if (!defined $r0 || !defined $parent) { $gs->fetch(0, $r); ($r0, $parent) = $gs->last_rev_commit; } @@ -1451,7 +1477,7 @@ sub make_log_entry { $log_entry{date} = parse_svn_date($log_entry{date}); $log_entry{author} = check_author($log_entry{author}); $log_entry{log} .= "\n"; - if (defined $headrev && $self->{-use_svm_props}) { + if (defined $headrev && $self->use_svm_props) { my ($uuid, $r) = $headrev =~ m{^([a-f\d\-]{30,}):(\d+)$}; if ($uuid ne $self->{svm}->{uuid}) { die "UUID mismatch on SVM path:\n", @@ -1556,7 +1582,7 @@ sub rev_db_set { $SIG{USR1} = $SIG{USR2} = sub { $sig = $_[0] }; } $LOCKFILES{$db_lock} = 1; - if ($_no_metadata) { + if ($self->no_metadata) { copy($db, $db_lock) or die "rev_db_set(@_): ", "Failed to copy: ", "$db => $db_lock ($!)\n"; From 93f2689ccdf7b62864aac40097bfd51328fae5b7 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 11 Feb 2007 01:20:26 -0800 Subject: [PATCH 142/201] git-svn: use private $GIT_DIR/svn/config file more Switch max_rev storage over to using it for globbing branches and tags. Signed-off-by: Eric Wong --- git-svn.perl | 53 +++++++++++++++++++++------------------------------- 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index becf2e0d89..b62cc067d8 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -738,16 +738,10 @@ sub fetch_all { foreach my $t (qw/branches tags/) { defined $remote->{$t} or next; push @globs, $remote->{$t}; - my $f = "$ENV{GIT_DIR}/svn/.$uuid.$t"; - if (open my $fh, '<', $f) { - chomp(my $max_rev = <$fh>); - close $fh or die "Error closing $f: $!\n"; - - if ($max_rev !~ /^\d+$/) { - die "$max_rev (in $f) is not an integer!\n"; - } - $remote->{$t}->{max_rev} = $max_rev; - $base = $max_rev if ($max_rev < $base); + my $max_rev = eval { tmp_config(qw/--int --get/, + "svn-remote.$repo_id.${t}-maxRev") }; + if (defined $max_rev && ($max_rev < $base)) { + $base = $max_rev; } } @@ -774,6 +768,7 @@ sub read_all_remotes { my ($p, $g) = ($3, $4); my $rs = $r->{$1}->{$2} = { t => $2, + remote => $1, path => Git::SVN::GlobSpec->new($p), ref => Git::SVN::GlobSpec->new($g) }; if (length($rs->{ref}->{right}) != 0) { @@ -951,8 +946,8 @@ sub set_svm_vars { # see if we have it in our config, first: eval { $self->{svm} = { - source => $self->tmp_config('--get', "$section.svm-source"), - uuid => $self->tmp_config('--get', "$section.svm-uuid"), + source => tmp_config('--get', "$section.svm-source"), + uuid => tmp_config('--get', "$section.svm-uuid"), } }; return $ra if ($self->{svm}->{source} && $self->{svm}->{uuid}); @@ -968,12 +963,12 @@ sub set_svm_vars { # username is of no interest $src =~ s{!$}{}; $src =~ s{(^[a-z\+]*://)[^/@]*@}{$1}; - $self->tmp_config('--add', "$section.svm-source", $src); + tmp_config('--add', "$section.svm-source", $src); my $uuid = $props->{'svm:uuid'}; $uuid =~ m{^[0-9a-f\-]{30,}$} or die "doesn't look right - svm:uuid is '$uuid'\n"; - $self->tmp_config('--add', "$section.svm-uuid", $uuid); + tmp_config('--add', "$section.svm-uuid", $uuid); $self->{svm} = { source => $src , uuid => $uuid }; } @@ -1079,18 +1074,19 @@ sub get_fetch_range { } sub tmp_config { - my ($self, @args) = @_; - unless (-f $self->{config}) { - open my $fh, '>', $self->{config} or - die "Can't open $self->{config}: $!\n"; + my (@args) = @_; + my $config = "$ENV{GIT_DIR}/svn/config"; + unless (-f $config) { + open my $fh, '>', $config or + die "Can't open $config: $!\n"; print $fh "; This file is used internally by git-svn\n" or - die "Couldn't write to $self->{config}: $!\n"; + die "Couldn't write to $config: $!\n"; print $fh "; You should not have to edit it\n" or - die "Couldn't write to $self->{config}: $!\n"; - close $fh or die "Couldn't close $self->{config}: $!\n"; + die "Couldn't write to $config: $!\n"; + close $fh or die "Couldn't close $config: $!\n"; } my $old_config = $ENV{GIT_CONFIG}; - $ENV{GIT_CONFIG} = $self->{config}; + $ENV{GIT_CONFIG} = $config; $@ = undef; my @ret = eval { command('config', @args) }; my $err = $@; @@ -2673,16 +2669,9 @@ sub gs_fetch_loop_common { } } foreach my $g (@$globs) { - my $f = "$ENV{GIT_DIR}/svn/." . - $self->uuid . ".$g->{t}"; - open my $fh, '>', "$f.tmp" or - die "Can't open $f.tmp for writing: $!"; - print $fh "$r\n" or - die "Couldn't write to $f: $!\n"; - close $fh or die "Error closing $f: $!\n"; - rename "$f.tmp", $f or - die "Couldn't rename ", - "$f.tmp => $f: $!\n"; + my $k = "svn-remote.$g->{remote}." . + "$g->{t}-maxRev"; + Git::SVN::tmp_config($k, $r); } } # pre-fill the .rev_db since it'll eventually get filled in From 97ae091169b233ecd80eb5ef2da80145f8c724f7 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 11 Feb 2007 15:21:24 -0800 Subject: [PATCH 143/201] git-svn: extra safety for noMetadata and useSvmProps users Make sure we flush our userspace buffers and and fsync(2) .rev_db information to disk if we use these options because we really don't want to lose this information. Also, disallow --use-svm-props and --no-metadata from the command-line because history will be inconsistent if they're only used occasionally. If a user wants to use these options, they must be set in the config so they're always on. Signed-off-by: Eric Wong --- Documentation/git-svn.txt | 24 ++++++++++++++++++++---- git-svn.perl | 29 ++++++++++++++++++++++------- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 50dc6ac818..d45283a53f 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -316,17 +316,33 @@ for more information on using GIT_SVN_ID. config key: svn.followparent ---no-metadata:: +svn.noMetadata: +svn-remote..noMetadata: This gets rid of the git-svn-id: lines at the end of every commit. If you lose your .git/svn/git-svn/.rev_db file, git-svn will not be able to rebuild it and you won't be able to fetch again, either. This is fine for one-shot imports. - The 'git-svn log' command will not work on repositories using this, - either. + The 'git-svn log' command will not work on repositories using + this, either. Using this conflicts with the 'useSvmProps' + option for (hopefully) obvious reasons. -config key: svn.nometadata +svn.useSvmProps: +svn-remote..useSvmProps: + This allows git-svn to re-map repository URLs and UUIDs from + mirrors created using SVN::Mirror (or svk) for metadata. + + If an SVN revision has a property, "svm:headrev", it is likely + that the revision was created by SVN::Mirror (also used by SVK). + The property contains a repository UUID and a revision. We want + to make it look like we are mirroring the original URL, so + introduce a helper function that returns the original identity + URL and UUID, and use it when generating metadata in commit + messages. + + Using this conflicts with the 'noMetadata' option for + (hopefully) obvious reasons. -- diff --git a/git-svn.perl b/git-svn.perl index b62cc067d8..fdecffbbc7 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -63,8 +63,8 @@ BEGIN my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent, 'authors-file|A=s' => \$_authors, 'repack:i' => \$Git::SVN::_repack, - 'no-metadata' => \$Git::SVN::_no_metadata, - 'use-svm-props|svm-props' => \$Git::SVN::_use_svm_props, + 'noMetadata' => \$Git::SVN::_no_metadata, + 'useSvmProps' => \$Git::SVN::_use_svm_props, 'quiet|q' => \$_q, 'repack-flags|repack-args|repack-opts=s' => \$Git::SVN::_repack_flags, @@ -606,9 +606,14 @@ sub load_authors { sub read_repo_config { return unless -d $ENV{GIT_DIR}; my $opts = shift; + my @config_only; foreach my $o (keys %$opts) { + # if we have mixedCase and a long option-only, then + # it's a config-only variable that we don't need for + # the command-line. + push @config_only, $o if ($o =~ /[A-Z]/ && $o =~ /^[a-z]+$/i); my $v = $opts->{$o}; - my ($key) = ($o =~ /^([a-z\-]+)/); + my ($key) = ($o =~ /^([a-zA-Z\-]+)/); $key =~ s/-//g; my $arg = 'git-config'; $arg .= ' --int' if ($o =~ /[:=]i$/); @@ -623,6 +628,7 @@ sub read_repo_config { } } } + delete @$opts{@config_only} if @config_only; } sub extract_metadata { @@ -983,8 +989,8 @@ sub ra { my $ra = Git::SVN::Ra->new($self->{url}); if ($self->use_svm_props && !$self->{svm}) { if ($self->no_metadata) { - die "Can't have both --no-metadata and ", - "--use-svm-props options set!\n"; + die "Can't have both 'noMetadata' and ", + "'useSvmProps' options set!\n"; } $ra = $self->set_svm_vars($ra); $self->{-want_revprops} = 1; @@ -1566,7 +1572,7 @@ sub rebuild { # to a revision: (41 * rev) is the byte offset. # A record of 40 0s denotes an empty revision. # And yes, it's still pretty fast (faster than Tie::File). -# These files are disposable unless --no-metadata is set +# These files are disposable unless noMetadata or useSvmProps is set sub rev_db_set { my ($self, $rev, $commit, $update_ref) = @_; @@ -1578,7 +1584,12 @@ sub rev_db_set { $SIG{USR1} = $SIG{USR2} = sub { $sig = $_[0] }; } $LOCKFILES{$db_lock} = 1; - if ($self->no_metadata) { + my $sync; + + # both of these options make our .rev_db file very, very important + # and we can't afford to lose it because rebuild() won't work + if ($self->use_svm_props || $self->no_metadata) { + $sync = 1; copy($db, $db_lock) or die "rev_db_set(@_): ", "Failed to copy: ", "$db => $db_lock ($!)\n"; @@ -1599,6 +1610,10 @@ sub rev_db_set { } seek $fh, $offset, 0 or croak $!; print $fh $commit,"\n" or croak $!; + if ($sync) { + $fh->flush or die "Couldn't flush $db_lock: $!\n"; + $fh->sync or die "Couldn't sync $db_lock: $!\n"; + } close $fh or croak $!; if ($update_ref) { command_noisy('update-ref', '-m', "r$rev", From 26a62d57a27407132d48e91b3c8f455a5fb22e4b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 12 Feb 2007 13:25:25 -0800 Subject: [PATCH 144/201] git-svn: use separate, per-repository .rev_db files We need a separate .rev_db file for each repository we're tracking. This allows us to track the same logical path off multiple mirrors. We preserve a symlink to the old .rev_db (no-UUID) if we're (auto-)migrating from an old version to preserve backwards compatibility. Also, get rid of the uuid() wrapper since we cache UUID in our private config, and the SVN::Ra::get_uuid() function memoizes the return value per-connection. Signed-off-by: Eric Wong --- git-svn.perl | 187 ++++++++++++++++++++++++++----------- t/t9107-git-svn-migrate.sh | 11 +++ 2 files changed, 141 insertions(+), 57 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index fdecffbbc7..beebe3d954 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -736,7 +736,7 @@ sub fetch_all { my $url = $remote->{url}; my (@gs, @globs); my $ra = Git::SVN::Ra->new($url); - my $uuid = $ra->uuid; + my $uuid = $ra->get_uuid; my $head = $ra->get_latest_revnum; my $base = $head; @@ -937,7 +937,8 @@ sub new { $self->{url} = command_oneline('config', '--get', "svn-remote.$repo_id.url") or die "Failed to read \"svn-remote.$repo_id.url\" in config\n"; - if (-z $self->{db_path} && ::verify_ref($self->refname.'^0')) { + if ((-z $self->db_path || ! -e $self->db_path) && + ::verify_ref($self->refname.'^0')) { $self->rebuild; } $self; @@ -945,18 +946,36 @@ sub new { sub refname { "refs/remotes/$_[0]->{ref_id}" } -sub set_svm_vars { - my ($self, $ra) = @_; - my $section = "svn-remote.$self->{repo_id}"; +sub svm_uuid { + my ($self) = @_; + return $self->{svm}->{uuid} if $self->svm; + $self->ra; + unless ($self->{svm}) { + die "SVM UUID not cached, and reading remotely failed\n"; + } + $self->{svm}->{uuid}; +} +sub svm { + my ($self) = @_; + return $self->{svm} if $self->{svm}; + my $svm; # see if we have it in our config, first: eval { - $self->{svm} = { + my $section = "svn-remote.$self->{repo_id}"; + $svm = { source => tmp_config('--get', "$section.svm-source"), uuid => tmp_config('--get', "$section.svm-uuid"), } }; - return $ra if ($self->{svm}->{source} && $self->{svm}->{uuid}); + $self->{svm} = $svm if ($svm && $svm->{source} && $svm->{uuid}); + $self->{svm}; +} + +sub _set_svm_vars { + my ($self, $ra) = @_; + + return $ra if ($self->svm); # nope, make sure we're connected to the repository root: if ($ra->{repos_root} ne $self->{url}) { @@ -965,6 +984,8 @@ sub set_svm_vars { my $r = $ra->get_latest_revnum; my ($props) = ($ra->get_dir('', $r))[2]; if (my $src = $props->{'svm:source'}) { + my $section = "svn-remote.$self->{repo_id}"; + # don't know what a '!' is there for, also the # username is of no interest $src =~ s{!$}{}; @@ -984,6 +1005,24 @@ sub set_svm_vars { $ra; } +# this allows us to memoize our SVN::Ra UUID locally and avoid a +# remote lookup (useful for 'git svn log'). +sub ra_uuid { + my ($self) = @_; + unless ($self->{ra_uuid}) { + my $key = "svn-remote.$self->{repo_id}.uuid"; + my $uuid = eval { tmp_config('--get', $key) }; + if (!$@ && $uuid && $uuid =~ /^([a-f\d\-]{30,})$/) { + $self->{ra_uuid} = $uuid; + } else { + die "ra_uuid called without URL\n" unless $self->{url}; + $self->{ra_uuid} = $self->ra->get_uuid; + tmp_config('--add', $key, $self->{ra_uuid}); + } + } + $self->{ra_uuid}; +} + sub ra { my ($self) = shift; my $ra = Git::SVN::Ra->new($self->{url}); @@ -992,7 +1031,7 @@ sub ra { die "Can't have both 'noMetadata' and ", "'useSvmProps' options set!\n"; } - $ra = $self->set_svm_vars($ra); + $ra = $self->_set_svm_vars($ra); $self->{-want_revprops} = 1; } $ra; @@ -1048,10 +1087,14 @@ sub last_rev_commit { return ($rev, $c); } } + my $db_path = $self->db_path; + unless (-e $db_path) { + ($self->{last_rev}, $self->{last_commit}) = (undef, undef); + return (undef, undef); + } my $offset = -41; # from tail my $rl; - open my $fh, '<', $self->{db_path} or - croak "$self->{db_path} not readable: $!\n"; + open my $fh, '<', $db_path or croak "$db_path not readable: $!\n"; sysseek($fh, $offset, 2); # don't care for errors sysread($fh, $rl, 41) == 41 or return (undef, undef); chomp $rl; @@ -1062,7 +1105,7 @@ sub last_rev_commit { chomp $rl; } if ($c && $c ne $rl) { - die "$self->{db_path} and ", $self->refname, + die "$db_path and ", $self->refname, " inconsistent!:\n$c != $rl\n"; } my $rev = sysseek($fh, 0, 1) or croak $!; @@ -1187,7 +1230,7 @@ sub do_git_commit { } my $author = $log_entry->{author}; my ($name, $email) = (defined $::users{$author} ? @{$::users{$author}} - : ($author, "$author\@".$self->ra->uuid)); + : ($author, "$author\@".$self->ra->get_uuid)); $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name; $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email; $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_entry->{date}; @@ -1227,6 +1270,8 @@ sub do_git_commit { print "r$log_entry->{revision}"; if (defined $log_entry->{svm_revision}) { print " (\@$log_entry->{svm_revision})"; + $self->rev_db_set($log_entry->{svm_revision}, $commit, + 0, $self->svm_uuid); } print " = $commit ($self->{ref_id})\n"; if (defined $_repack && (--$_repack_nr == 0)) { @@ -1492,7 +1537,7 @@ sub make_log_entry { $log_entry{svm_revision} = $r; } else { $log_entry{metadata} = $self->full_url . "\@$rev " . - $self->ra->uuid; + $self->ra->get_uuid; } \%log_entry; } @@ -1531,7 +1576,16 @@ sub set_tree { sub rebuild { my ($self) = @_; - print "Rebuilding $self->{db_path} ...\n"; + my $db_path = $self->db_path; + if (-f $self->{db_root}) { + rename $self->{db_root}, $db_path or die + "rename $self->{db_root} => $db_path failed: $!\n"; + my ($dir, $base) = ($db_path =~ m#^(.*?)/?([^/]+)$#); + symlink $base, $self->{db_root} or die + "symlink $base => $self->{db_root} failed: $!\n"; + return; + } + print "Rebuilding $db_path ...\n"; my ($rev_list, $ctx) = command_output_pipe("rev-list", $self->refname); my $latest; my $full_url = $self->full_url; @@ -1558,7 +1612,7 @@ sub rebuild { print "r$rev = $c\n"; } command_close_pipe($rev_list, $ctx); - print "Done rebuilding $self->{db_path}\n"; + print "Done rebuilding $db_path\n"; } # rev_db: @@ -1574,31 +1628,8 @@ sub rebuild { # And yes, it's still pretty fast (faster than Tie::File). # These files are disposable unless noMetadata or useSvmProps is set -sub rev_db_set { - my ($self, $rev, $commit, $update_ref) = @_; - length $commit == 40 or croak "arg3 must be a full SHA1 hexsum\n"; - my ($db, $db_lock) = ($self->{db_path}, "$self->{db_path}.lock"); - my $sig; - if ($update_ref) { - $SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} = - $SIG{USR1} = $SIG{USR2} = sub { $sig = $_[0] }; - } - $LOCKFILES{$db_lock} = 1; - my $sync; - - # both of these options make our .rev_db file very, very important - # and we can't afford to lose it because rebuild() won't work - if ($self->use_svm_props || $self->no_metadata) { - $sync = 1; - copy($db, $db_lock) or die "rev_db_set(@_): ", - "Failed to copy: ", - "$db => $db_lock ($!)\n"; - } else { - rename $db, $db_lock or die "rev_db_set(@_): ", - "Failed to rename: ", - "$db => $db_lock ($!)\n"; - } - open my $fh, '+<', $db_lock or croak $!; +sub _rev_db_set { + my ($fh, $rev, $commit) = @_; my $offset = $rev * 41; # assume that append is the common case: seek $fh, 0, 2 or croak $!; @@ -1610,6 +1641,46 @@ sub rev_db_set { } seek $fh, $offset, 0 or croak $!; print $fh $commit,"\n" or croak $!; +} + +sub mkfile { + my ($path) = @_; + unless (-e $path) { + my ($dir, $base) = ($path =~ m#^(.*?)/?([^/]+)$#); + mkpath([$dir]) unless -d $dir; + open my $fh, '>>', $path or die "Couldn't create $path: $!\n"; + close $fh or die "Couldn't close (create) $path: $!\n"; + } +} + +sub rev_db_set { + my ($self, $rev, $commit, $update_ref, $uuid) = @_; + length $commit == 40 or die "arg3 must be a full SHA1 hexsum\n"; + my $db = $self->db_path($uuid); + my $db_lock = "$db.lock"; + my $sig; + if ($update_ref) { + $SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} = + $SIG{USR1} = $SIG{USR2} = sub { $sig = $_[0] }; + } + mkfile($db); + + $LOCKFILES{$db_lock} = 1; + my $sync; + # both of these options make our .rev_db file very, very important + # and we can't afford to lose it because rebuild() won't work + if ($self->use_svm_props || $self->no_metadata) { + $sync = 1; + copy($db, $db_lock) or die "rev_db_set(@_): ", + "Failed to copy: ", + "$db => $db_lock ($!)\n"; + } else { + rename $db, $db_lock or die "rev_db_set(@_): ", + "Failed to rename: ", + "$db => $db_lock ($!)\n"; + } + open my $fh, '+<', $db_lock or die "Couldn't open $db_lock: $!\n"; + _rev_db_set($fh, $rev, $commit); if ($sync) { $fh->flush or die "Couldn't flush $db_lock: $!\n"; $fh->sync or die "Couldn't sync $db_lock: $!\n"; @@ -1631,19 +1702,20 @@ sub rev_db_set { sub rev_db_max { my ($self) = @_; - my @stat = stat $self->{db_path} or - die "Couldn't stat $self->{db_path}: $!\n"; - ($stat[7] % 41) == 0 or - die "$self->{db_path} inconsistent size:$stat[7]\n"; + my $db_path = $self->db_path; + my @stat = stat $db_path or return 0; + ($stat[7] % 41) == 0 or die "$db_path inconsistent size: $stat[7]\n"; my $max = $stat[7] / 41; (($max > 0) ? $max - 1 : 0); } sub rev_db_get { - my ($self, $rev) = @_; + my ($self, $rev, $uuid) = @_; my $ret; my $offset = $rev * 41; - open my $fh, '<', $self->{db_path} or croak $!; + my $db_path = $self->db_path($uuid); + return undef unless -e $db_path; + open my $fh, '<', $db_path or croak $!; if (sysseek($fh, $offset, 0) == $offset) { my $read = sysread($fh, $ret, 40); $ret = undef if ($read != 40 || $ret eq ('0'x40)); @@ -1676,13 +1748,16 @@ sub _new { my $dir = "$ENV{GIT_DIR}/svn/$ref_id"; $_[3] = $path = '' unless (defined $path); mkpath([$dir]); - unless (-f "$dir/.rev_db") { - open my $fh, '>>', "$dir/.rev_db" or croak $!; - close $fh or croak $!; - } - bless { ref_id => $ref_id, dir => $dir, index => "$dir/index", + bless { + ref_id => $ref_id, dir => $dir, index => "$dir/index", path => $path, config => "$ENV{GIT_DIR}/svn/config", - db_path => "$dir/.rev_db", repo_id => $repo_id }, $class; + db_root => "$dir/.rev_db", repo_id => $repo_id }, $class; +} + +sub db_path { + my ($self, $uuid) = @_; + $uuid ||= $self->ra_uuid; + "$self->{db_root}.$uuid"; } sub uri_encode { @@ -2519,11 +2594,6 @@ sub get_commit_editor { $self->SUPER::get_commit_editor($log, $cb, @lock, $pool); } -sub uuid { - my ($self) = @_; - $self->{uuid} ||= $self->get_uuid; -} - sub gs_do_update { my ($self, $rev_a, $rev_b, $gs, $editor) = @_; my $new = ($rev_a == $rev_b); @@ -3140,6 +3210,9 @@ package Git::SVN::Migration; # - info/url may remain for backwards compatibility # - this is what we migrate up to this layout automatically, # - this will be used by git svn init on single branches +# v3.1 layout (auto migrated): +# - .rev_db => .rev_db.$UUID, .rev_db will remain as a symlink +# for backwards compatibility # # v4 layout: .git/svn/$repo_id/$id, refs/remotes/$repo_id/$id # - this is only created for newly multi-init-ed diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index 8376429bcb..9f107ad7bf 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -96,5 +96,16 @@ test_expect_success 'migrate --minimize on old multi-inited layout' " grep '^:refs/remotes/git-svn' fetch.out " +test_expect_success ".rev_db auto-converted to .rev_db.UUID" " + git-svn fetch -i trunk && + expect=$GIT_DIR/svn/trunk/.rev_db.* && + test -n \"\$expect\" && + mv \$expect $GIT_DIR/svn/trunk/.rev_db && + git-svn fetch -i trunk && + test -L $GIT_DIR/svn/trunk/.rev_db && + test -f \$expect && + cmp \$expect $GIT_DIR/svn/trunk/.rev_db + " + test_done From c3560e535c67b4084852da00507ff4b7fdf98ffc Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 12 Feb 2007 16:03:32 -0800 Subject: [PATCH 145/201] git-svn: write the highest maxRex out for branches and tags Even if nothing touched paths we care about in a fetch; increment the maxRev like we do with rev_db since we don't like having to run get_log on revisions we've seen before. Signed-off-by: Eric Wong --- git-svn.perl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/git-svn.perl b/git-svn.perl index beebe3d954..b7e46e5b0b 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2765,6 +2765,10 @@ sub gs_fetch_loop_common { next if defined $gs->rev_db_get($max); $gs->rev_db_set($max, 0 x40); } + foreach my $g (@$globs) { + my $k = "svn-remote.$g->{remote}.$g->{t}-maxRev"; + Git::SVN::tmp_config($k, $max); + } last if $max >= $head; $min = $max + 1; $max += $inc; From db03cd24a155a727349a47ce0e5ba3f4c4032cb8 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 13 Feb 2007 00:38:02 -0800 Subject: [PATCH 146/201] git-svn: handle multi-init without --trunk, UseSvmProps fixes multi-init did not write a svn-remote..url config entry without a --trunk argument. Also, The svm:mirror property is used by SVN::Mirror to track the path of the repository that we are mirroring. We need to append that to the source (which is (presumably) just the URL of the repository root). Lastly, we now look harder for svm:(source|mirror|uuid) properties in sub and parent directories. Since our relative path could be tweaked. Signed-off-by: Eric Wong --- git-svn.perl | 120 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 85 insertions(+), 35 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index b7e46e5b0b..d55691216c 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -367,8 +367,7 @@ sub cmd_multi_init { sub cmd_multi_fetch { my $remotes = Git::SVN::read_all_remotes(); foreach my $repo_id (sort keys %$remotes) { - if ($remotes->{$repo_id}->{url} && - $remotes->{$repo_id}->{fetch}) { + if ($remotes->{$repo_id}->{url}) { Git::SVN::fetch_all($repo_id, $remotes); } } @@ -483,6 +482,17 @@ sub complete_url_ls_init { $gs = Git::SVN->init($url, $path, undef, $ref, 1); } if ($gs) { + my $k = "svn-remote.$gs->{repo_id}.url"; + my $orig_url = eval { + command_oneline(qw/config --get/, $k) + }; + if ($orig_url && ($orig_url ne $gs->{url})) { + die "$k already set: $orig_url\n", + "wanted to set to: $gs->{url}\n"; + } + unless ($orig_url) { + command_oneline('config', $k, $gs->{url}); + } $remote_id = $gs->{repo_id}; last; } @@ -751,13 +761,15 @@ sub fetch_all { } } - foreach my $p (sort keys %$fetch) { - my $gs = Git::SVN->new($fetch->{$p}, $repo_id, $p); - my $lr = $gs->rev_db_max; - if (defined $lr) { - $base = $lr if ($lr < $base); + if ($fetch) { + foreach my $p (sort keys %$fetch) { + my $gs = Git::SVN->new($fetch->{$p}, $repo_id, $p); + my $lr = $gs->rev_db_max; + if (defined $lr) { + $base = $lr if ($lr < $base); + } + push @gs, $gs; } - push @gs, $gs; } $ra->gs_fetch_loop_common($base, $head, \@gs, \@globs); } @@ -974,35 +986,69 @@ sub svm { sub _set_svm_vars { my ($self, $ra) = @_; + return $ra if $self->svm; - return $ra if ($self->svm); - - # nope, make sure we're connected to the repository root: - if ($ra->{repos_root} ne $self->{url}) { - $ra = Git::SVN::Ra->new($ra->{repos_root}); - } - my $r = $ra->get_latest_revnum; - my ($props) = ($ra->get_dir('', $r))[2]; - if (my $src = $props->{'svm:source'}) { - my $section = "svn-remote.$self->{repo_id}"; - - # don't know what a '!' is there for, also the - # username is of no interest - $src =~ s{!$}{}; - $src =~ s{(^[a-z\+]*://)[^/@]*@}{$1}; - tmp_config('--add', "$section.svm-source", $src); - + my @err = ( "useSvmProps set, but failed to read SVM properties\n", + "(svm:source, svm:mirror, svm:mirror) ", + "from the following URLs:\n" ); + sub read_svm_props { + my ($self, $props) = @_; + my $src = $props->{'svm:source'}; + my $mirror = $props->{'svm:mirror'}; my $uuid = $props->{'svm:uuid'}; + return undef if (!$src || !$mirror || !$uuid); + + chomp($src, $mirror, $uuid); + $uuid =~ m{^[0-9a-f\-]{30,}$} or die "doesn't look right - svm:uuid is '$uuid'\n"; - tmp_config('--add', "$section.svm-uuid", $uuid); + # don't know what a '!' is there for, also the + # username is of no interest + $src =~ s{/?!$}{$mirror}; + $src =~ s{/+$}{}; # no trailing slashes please + $src =~ s{(^[a-z\+]*://)[^/@]*@}{$1}; + my $section = "svn-remote.$self->{repo_id}"; + tmp_config('--add', "$section.svm-source", $src); + tmp_config('--add', "$section.svm-uuid", $uuid); $self->{svm} = { source => $src , uuid => $uuid }; + return 1; } - if ($ra->{repos_root} ne $self->{url}) { - $ra = Git::SVN::Ra->new($self->{url}); + + my $r = $ra->get_latest_revnum; + my $path = $self->{path}; + my @tried_a = ($path); + while (length $path) { + if ($self->read_svm_props(($ra->get_dir($path, $r))[2])) { + return $ra; + } + $path =~ s#/?[^/]+$## && push @tried_a, $path; } - $ra; + if ($self->read_svm_props(($ra->get_dir('', $r))[2])) { + return $ra; + } + + if ($ra->{repos_root} eq $self->{url}) { + die @err, map { " $self->{url}/$_\n" } @tried_a, "\n"; + } + + # nope, make sure we're connected to the repository root: + my $ok; + my @tried_b; + $path = $ra->{svn_path}; + $path =~ s#/?[^/]+$##; # we already tried this one above + $ra = Git::SVN::Ra->new($ra->{repos_root}); + while (length $path) { + $ok = $self->read_svm_props(($ra->get_dir($path, $r))[2]); + last if $ok; + $path =~ s#/?[^/]+$## && push @tried_b, $path; + } + $ok = $self->read_svm_props(($ra->get_dir('', $r))[2]) unless $ok; + if (!$ok) { + die @err, map { " $self->{url}/$_\n" } @tried_a, "\n", + map { " $ra->{url}/$_\n" } @tried_b, "\n" + } + Git::SVN::Ra->new($self->{url}); } # this allows us to memoize our SVN::Ra UUID locally and avoid a @@ -1228,11 +1274,9 @@ sub do_git_commit { croak "$log_entry->{revision} = $c already exists! ", "Why are we refetching it?\n"; } - my $author = $log_entry->{author}; - my ($name, $email) = (defined $::users{$author} ? @{$::users{$author}} - : ($author, "$author\@".$self->ra->get_uuid)); - $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name; - $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email; + $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $log_entry->{name}; + $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = + $log_entry->{email}; $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_entry->{date}; my $tree = $log_entry->{tree}; @@ -1522,8 +1566,10 @@ sub make_log_entry { close $un or croak $!; $log_entry{date} = parse_svn_date($log_entry{date}); - $log_entry{author} = check_author($log_entry{author}); $log_entry{log} .= "\n"; + my $author = $log_entry{author} = check_author($log_entry{author}); + my ($name, $email) = defined $::users{$author} ? @{$::users{$author}} + : ($author, undef); if (defined $headrev && $self->use_svm_props) { my ($uuid, $r) = $headrev =~ m{^([a-f\d\-]{30,}):(\d+)$}; if ($uuid ne $self->{svm}->{uuid}) { @@ -1535,10 +1581,14 @@ sub make_log_entry { $full_url .= "/$self->{path}" if length $self->{path}; $log_entry{metadata} = "$full_url\@$r $uuid"; $log_entry{svm_revision} = $r; + $email ||= "$author\@$uuid" } else { $log_entry{metadata} = $self->full_url . "\@$rev " . $self->ra->get_uuid; + $email ||= "$author\@" . $self->ra->get_uuid; } + $log_entry{name} = $name; + $log_entry{email} = $email; \%log_entry; } From 2edb9c5cf98e1a65c775ede6fc5b10a15bb94384 Mon Sep 17 00:00:00 2001 From: "sam@vilain.net" Date: Tue, 5 Dec 2006 16:17:38 +1100 Subject: [PATCH 147/201] git-svn: make test for SVK mirror path import A manual test that sets up a repository that looks like an SVK depot, and then imports it to check that it looks like we mirrored the 'original' source. There is also a minor modification to the git-svn test library shell file which sets a variable for the subversion repository's filesystem path. [ew: made some of the tests stricter and more thorough] Signed-off-by: Eric Wong --- t/lib-git-svn.sh | 2 +- t/t9109-git-svn-svk-mirrorpaths.sh | 104 +++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 1 deletion(-) create mode 100755 t/t9109-git-svn-svk-mirrorpaths.sh diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index 67d08cf740..27ad3b70f9 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh @@ -42,9 +42,9 @@ then exit fi +rawsvnrepo="$svnrepo" svnrepo="file://$svnrepo" - poke() { perl -e '@x = stat($ARGV[0]); utime($x[8], $x[9] + 1, $ARGV[0])' "$1" } diff --git a/t/t9109-git-svn-svk-mirrorpaths.sh b/t/t9109-git-svn-svk-mirrorpaths.sh new file mode 100755 index 0000000000..0e0ba3df39 --- /dev/null +++ b/t/t9109-git-svn-svk-mirrorpaths.sh @@ -0,0 +1,104 @@ +#!/bin/sh +# +# Copyright (c) 2006 Sam Vilian +# + +test_description='git-svn on SVK mirror paths' +. ./lib-git-svn.sh + +# ok, people who don't have SVK installed probably don't care about +# this test. + +# we set up the repository manually, because even if SVK is installed +# it is difficult to use it in a way that is idempotent. + +# we are not yet testing merge tickets.. + +uuid=b00bface-b1ff-c0ff-f0ff-b0bafe775e1e +url=https://really.slow.server.com/foobar + +test_expect_success 'initialize repo' " + git config svn-remote.svn.useSvmProps true && + + echo '#!/bin/sh' > $rawsvnrepo/hooks/pre-revprop-change && + echo 'exit 0' >> $rawsvnrepo/hooks/pre-revprop-change && + chmod +x $rawsvnrepo/hooks/pre-revprop-change && + + mkdir import && + cd import && + mkdir local && + echo hello > local/readme && + svn import -m 'random local work' . $svnrepo && + cd .. && + + svn co $svnrepo wc && + cd wc && + mkdir -p mirror/foobar && + svn add mirror && + svn ps svm:source $url mirror/foobar && + svn ps svm:uuid $uuid mirror/foobar && + svn ps svm:mirror / mirror/foobar && + svn commit -m 'setup mirror/foobar as mirror of upstream' && + svn ps -r 2 --revprop svm:headrev $uuid:0 $svnrepo && + + mkdir mirror/foobar/trunk + echo hello, world > mirror/foobar/trunk/readme && + svn add mirror/foobar/trunk && + svn commit -m 'first upstream revision' && + svn ps -r 3 --revprop svm:headrev $uuid:1 $svnrepo && + + svn up && + svn mkdir mirror/foobar/branches && + svn cp mirror/foobar/trunk mirror/foobar/branches/silly && + svn commit -m 'make branch for silliness' && + svn ps -r 4 --revprop svm:headrev $uuid:2 $svnrepo && + + svn up && + echo random untested feature >> mirror/foobar/trunk/readme && + svn commit -m 'add a c00l feature to trunk' && + svn ps -r 5 --revprop svm:headrev $uuid:3 $svnrepo && + + svn up && + echo bug fix >> mirror/foobar/branches/silly/readme && + svn commit -m 'fix a bug' && + svn ps -r 6 --revprop svm:headrev $uuid:4 $svnrepo && + + svn mkdir mirror/foobar/tags && + svn cp mirror/foobar/branches/silly mirror/foobar/tags/blah-1.0 && + svn commit -m 'make a release' && + svn ps -r 7 --revprop svm:headrev $uuid:5 $svnrepo && + + cd .. + " + +test_expect_success 'multi-init an SVK mirror path' " + git-svn multi-init -T trunk -t tags -b branches $svnrepo/mirror/foobar + " + +test_expect_success 'multi-fetch an SVK mirror path' "git-svn multi-fetch" + +test_expect_success 'got tag history OK' " + test \`git-log --pretty=oneline remotes/tags/blah-1.0 | wc -l\` -eq 3 + " + +test_expect_success 're-wrote git-svn-id URL, revision and UUID' " + git cat-file commit refs/remotes/trunk | \ + fgrep 'git-svn-id: $url/mirror/foobar/trunk@3 $uuid' && + git cat-file commit refs/remotes/tags/blah-1.0 | \ + fgrep 'git-svn-id: $url/mirror/foobar/tags/blah-1.0@5 $uuid' + git cat-file commit refs/remotes/silly | \ + fgrep 'git-svn-id: $url/mirror/foobar/branches/silly@4 $uuid' + " + +test_expect_success 're-wrote author e-mail domain UUID' " + test \`git log --pretty=fuller trunk | \ + grep '<.*@.*>' | fgrep '@$uuid>' | wc -l\` -eq 4 && + test \`git log --pretty=fuller remotes/silly | \ + grep '<.*@.*>' | fgrep '@$uuid>' | wc -l\` -eq 6 && + test \`git log --pretty=fuller remotes/tags/blah-1.0 | \ + grep '<.*@.*>' | fgrep '@$uuid>' | wc -l\` -eq 6 + " + +test_debug 'gitk --all &' + +test_done From a8ae26235ced15d8ce129a8ff72c558b8a567813 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 13 Feb 2007 14:22:11 -0800 Subject: [PATCH 148/201] git-svn: make dcommit usable for glob users * dcommit no longer requires the correct -i/GIT_SVN_ID option passed to it. Since you're committing from HEAD (or another commit that is a parent of HEAD), you'll be able to find a commit with metadata information containing the SVN URL that your HEAD was descended from anyways. * I don't think dcommit ever worked for people using the noMetadata option; so I don't think relying on metadata is an issue. * useSvmProps users shouldn't commit to SVN::Mirror created repositories anyways, right? * Users of globbing should automatically be able to commit to paths that are not explicitly set in .git/config Signed-off-by: Eric Wong --- git-svn.perl | 73 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 22 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index d55691216c..09c0aba8ea 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -276,11 +276,27 @@ sub cmd_set_tree { sub cmd_dcommit { my $head = shift; - my $gs = Git::SVN->new; $head ||= 'HEAD'; - my @refs = command(qw/rev-list --no-merges/, $gs->refname."..$head"); + my ($url, $rev, $uuid); + my ($fh, $ctx) = command_output_pipe(qw/rev-list --no-merges/, $head); + my @refs; + my $c; + while (<$fh>) { + $c = $_; + chomp $c; + ($url, $rev, $uuid) = cmt_metadata($c); + last if (defined $url && defined $rev && defined $uuid); + unshift @refs, $c; + } + close $fh; # most likely breaking the pipe + unless (defined $url && defined $rev && defined $uuid) { + die "Unable to determine upstream SVN information from ", + "$head history:\n $ctx\n"; + } + my $gs = Git::SVN->find_by_url($url) or + die "Can't determine fetch information for $url\n"; my $last_rev; - foreach my $d (reverse @refs) { + foreach my $d (@refs) { if (!verify_ref("$d~1")) { fatal "Commit $d\n", "has no parent commit, and therefore ", @@ -300,13 +316,13 @@ sub cmd_dcommit { } else { my %ed_opts = ( r => $last_rev, log => get_commit_entry($d)->{log}, - ra => $gs->ra, + ra => Git::SVN::Ra->new($url), tree_a => "$d~1", tree_b => $d, editor_cb => sub { print "Committed r$_[0]\n"; $last_rev = $_[0]; }, - svn_path => $gs->{path} ); + svn_path => ''); if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) { print "No changes\n$d~1 == $d\n"; } @@ -904,6 +920,35 @@ sub init_remote_config { $self->{url} = $url; } +sub find_by_url { # repos_root and, path are optional + my ($class, $full_url, $repos_root, $path) = @_; + my $remotes = read_all_remotes(); + if (defined $full_url && defined $repos_root && !defined $path) { + $path = $full_url; + $path =~ s#^\Q$repos_root\E(?:/|$)##; + } + foreach my $repo_id (keys %$remotes) { + my $u = $remotes->{$repo_id}->{url} or next; + next if defined $repos_root && $repos_root ne $u; + + my $fetch = $remotes->{$repo_id}->{fetch} || {}; + foreach (qw/branches tags/) { + resolve_local_globs($u, $fetch, + $remotes->{$repo_id}->{$_}); + } + my $p = $path; + unless (defined $p) { + $p = $full_url; + $p =~ s#^\Q$u\E(?:/|$)## or next; + } + foreach my $f (keys %$fetch) { + next if $f ne $p; + return Git::SVN->new($fetch->{$f}, $repo_id, $f); + } + } + undef; +} + sub init { my ($class, $url, $path, $repo_id, $ref_id, $no_write) = @_; my $self = _new($class, $repo_id, $ref_id, $path); @@ -1387,23 +1432,7 @@ sub find_parent_branch { print STDERR "Found possible branch point: ", "$new_url => ", $self->full_url, ", $r\n"; $branch_from =~ s#^/##; - my $remotes = read_all_remotes(); - my $gs; - foreach my $repo_id (keys %$remotes) { - my $u = $remotes->{$repo_id}->{url} or next; - next if $url ne $u; - my $fetch = $remotes->{$repo_id}->{fetch}; - foreach (qw/branches tags/) { - resolve_local_globs($url, $fetch, - $remotes->{$repo_id}->{$_}); - } - foreach my $f (keys %$fetch) { - next if $f ne $branch_from; - $gs = Git::SVN->new($fetch->{$f}, $repo_id, $f); - last; - } - last if $gs; - } + my $gs = Git::SVN->find_by_url($new_url, $repos_root, $branch_from); unless ($gs) { my $ref_id = $self->{ref_id}; $ref_id =~ s/\@\d+$//; From ce207c7ad1604c6afd5014051641e40f346a59c6 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 13 Feb 2007 15:56:08 -0800 Subject: [PATCH 149/201] git-svn: include merges when calling rev-list for decommit Merge commits can be created when following certain parents, (most notably 'R' cases) and we definitely don't want to exclude them. Signed-off-by: Eric Wong --- git-svn.perl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-svn.perl b/git-svn.perl index 09c0aba8ea..66653f9eb4 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -278,7 +278,7 @@ sub cmd_dcommit { my $head = shift; $head ||= 'HEAD'; my ($url, $rev, $uuid); - my ($fh, $ctx) = command_output_pipe(qw/rev-list --no-merges/, $head); + my ($fh, $ctx) = command_output_pipe('rev-list', $head); my @refs; my $c; while (<$fh>) { From 3bc718ba66f8b101b4017e778138660d66829312 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 13 Feb 2007 17:09:40 -0800 Subject: [PATCH 150/201] git-svn: usability fixes for the 'git svn log' command Similar in spirit to the recent dcommit change, we now look at 'HEAD' by default to look for a GIT_SVN_ID so the user won't have to pass -i argument. We are also more tolerant of of people passing bare remote names as a result (just $GIT_SVN_ID without the -i) Signed-off-by: Eric Wong --- git-svn.perl | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 66653f9eb4..fb2c864a39 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -3033,8 +3033,25 @@ sub log_use_color { } sub git_svn_log_cmd { - my ($r_min, $r_max) = @_; - my $gs = Git::SVN->_new; + my ($r_min, $r_max, @args) = @_; + my $head = 'HEAD'; + foreach my $x (@args) { + last if $x eq '--'; + next unless ::verify_ref("$x^0"); + $head = $x; + last; + } + + my $url; + my ($fh, $ctx) = command_output_pipe('rev-list', $head); + while (<$fh>) { + chomp; + $url = (::cmt_metadata($_))[0]; + last if defined $url; + } + close $fh; # break the pipe + + my $gs = Git::SVN->find_by_url($url) || Git::SVN->_new; my @cmd = (qw/log --abbrev-commit --pretty=raw --default/, $gs->refname); push @cmd, '-r' unless $non_recursive; @@ -3227,7 +3244,7 @@ sub cmd_show_log { } config_pager(); - @args = (git_svn_log_cmd($r_min, $r_max), @args); + @args = (git_svn_log_cmd($r_min, $r_max, @args), @args); my $log = command_output_pipe(@args); run_pager(); my (@k, $c, $d); From ccb6b6f5b50bb32f90222a3e801a1901bf0b5657 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 13 Feb 2007 17:38:58 -0800 Subject: [PATCH 151/201] t910*: s/repo-config/config/g; poke around possible race conditions Some of the repo-config => config renaming missed the git-svn tests; so I'm just renaming them to be consisten with the rest of the modern git. Also, some of the newer tests didn't have 'poke' in them to workaround race conditions on fast machines. This adds places where they can _possibly_ occur; but I don't have fast enough hardware to trigger them. Signed-off-by: Eric Wong --- t/t9100-git-svn-basic.sh | 8 ++++---- t/t9104-git-svn-follow-parent.sh | 9 +++++---- t/t9107-git-svn-migrate.sh | 14 +++++++------- t/t9108-git-svn-glob.sh | 5 +++++ t/t9109-git-svn-svk-mirrorpaths.sh | 2 ++ 5 files changed, 23 insertions(+), 15 deletions(-) diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index 8b6c8ffe10..7dcfc7e7db 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -214,7 +214,7 @@ EOF test_expect_success "$name" "diff -u a expected" test_expect_failure 'exit if remote refs are ambigious' " - git-repo-config --add svn-remote.svn.fetch \ + git-config --add svn-remote.svn.fetch \ bar:refs/remotes/git-svn && git-svn migrate " @@ -222,7 +222,7 @@ test_expect_failure 'exit if remote refs are ambigious' " test_expect_failure 'exit if init-ing a would clobber a URL' " svnadmin create ${PWD}/svnrepo2 && svn mkdir -m 'mkdir bar' ${svnrepo}2/bar && - git-repo-config --unset svn-remote.svn.fetch \ + git-config --unset svn-remote.svn.fetch \ '^bar:refs/remotes/git-svn$' && git-svn init ${svnrepo}2/bar " @@ -230,9 +230,9 @@ test_expect_failure 'exit if init-ing a would clobber a URL' " test_expect_success \ 'init allows us to connect to another directory in the same repo' " git-svn init -i bar $svnrepo/bar && - git repo-config --get svn-remote.svn.fetch \ + git config --get svn-remote.svn.fetch \ '^bar:refs/remotes/bar$' && - git repo-config --get svn-remote.svn.fetch \ + git config --get svn-remote.svn.fetch \ '^:refs/remotes/git-svn$' " diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 53f5a925ac..bd4f366e86 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -39,10 +39,10 @@ test_expect_success 'init and fetch a moved directory' " " test_expect_success 'init and fetch from one svn-remote' " - git-repo-config svn-remote.svn.url $svnrepo && - git-repo-config --add svn-remote.svn.fetch \ + git-config svn-remote.svn.url $svnrepo && + git-config --add svn-remote.svn.fetch \ trunk:refs/remotes/svn/trunk && - git-repo-config --add svn-remote.svn.fetch \ + git-config --add svn-remote.svn.fetch \ thunk:refs/remotes/svn/thunk && git-svn fetch -i svn/thunk && test \"\`git-rev-parse --verify refs/remotes/svn/trunk\`\" \ @@ -54,7 +54,7 @@ test_expect_success 'init and fetch from one svn-remote' " test_expect_success 'follow deleted parent' " svn cp -m 'resurrecting trunk as junk' \ -r2 $svnrepo/trunk $svnrepo/junk && - git-repo-config --add svn-remote.svn.fetch \ + git-config --add svn-remote.svn.fetch \ junk:refs/remotes/svn/junk && git-svn fetch -i svn/thunk && git-svn fetch -i svn/junk && @@ -124,6 +124,7 @@ test_expect_success 'follow-parent avoids deleting relevant info' " svn mv t native/t && for i in a b c; do svn mv \$i.pm native/\$i.pm; done && echo z >> native/t/c.t && + poke native/t/c.t && svn commit -m 'reorg test' && cd .. && git-svn init -i r9270-t \ diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index 9f107ad7bf..d26c355f05 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -34,14 +34,14 @@ test_expect_success 'initialize old-style (v0) git-svn layout' " ! test -d $GIT_DIR/git-svn && git-rev-parse --verify refs/remotes/git-svn^0 && git-rev-parse --verify refs/remotes/svn^0 && - test \`git repo-config --get svn-remote.svn.url\` = '$svnrepo' && - test \`git repo-config --get svn-remote.svn.fetch\` = \ + test \`git config --get svn-remote.svn.url\` = '$svnrepo' && + test \`git config --get svn-remote.svn.fetch\` = \ ':refs/remotes/git-svn' " test_expect_success 'initialize a multi-repository repo' " git-svn multi-init $svnrepo -T trunk -t tags -b branches && - git-repo-config --get-all svn-remote.svn.fetch > fetch.out && + git-config --get-all svn-remote.svn.fetch > fetch.out && grep '^trunk:refs/remotes/trunk$' fetch.out && test -n \"\`git-config --get svn-remote.svn.branches \ '^branches/\*:refs/remotes/\*$'\`\" && @@ -73,8 +73,8 @@ test_expect_success 'multi-fetch works on partial urls + paths' " " test_expect_success 'migrate --minimize on old multi-inited layout' " - git repo-config --unset-all svn-remote.svn.fetch && - git repo-config --unset-all svn-remote.svn.url && + git config --unset-all svn-remote.svn.fetch && + git config --unset-all svn-remote.svn.url && rm -rf $GIT_DIR/svn && for i in \`cat fetch.out\`; do path=\`expr \$i : '\\([^:]*\\):.*$'\` @@ -85,8 +85,8 @@ test_expect_success 'migrate --minimize on old multi-inited layout' " echo $svnrepo\$path > $GIT_DIR/svn/\$ref/info/url ) || exit 1; done && git-svn migrate --minimize && - test -z \"\`git-repo-config -l |grep -v '^svn-remote\.git-svn\.'\`\" && - git-repo-config --get-all svn-remote.svn.fetch > fetch.out && + test -z \"\`git-config -l |grep -v '^svn-remote\.git-svn\.'\`\" && + git-config --get-all svn-remote.svn.fetch > fetch.out && grep '^trunk:refs/remotes/trunk$' fetch.out && grep '^branches/a:refs/remotes/a$' fetch.out && grep '^branches/b:refs/remotes/b$' fetch.out && diff --git a/t/t9108-git-svn-glob.sh b/t/t9108-git-svn-glob.sh index 47cccdfd0e..be21fc13b7 100755 --- a/t/t9108-git-svn-glob.sh +++ b/t/t9108-git-svn-glob.sh @@ -23,14 +23,19 @@ test_expect_success 'test refspec globbing' " svn commit -m 'start a new branch' && svn up && echo 'hi' >> branches/start/src/b/readme && + poke branches/start/src/b/readme && echo 'hey' >> branches/start/src/a/readme && + poke branches/start/src/a/readme && svn commit -m 'hi' && svn up && svn cp branches/start tags/end && echo 'bye' >> tags/end/src/b/readme && + poke tags/end/src/b/readme && echo 'aye' >> tags/end/src/a/readme && + poke tags/end/src/a/readme && svn commit -m 'the end' && echo 'byebye' >> tags/end/src/b/readme && + poke tags/end/src/b/readme && svn commit -m 'nothing to see here' cd .. && git config --add svn-remote.svn.url $svnrepo && diff --git a/t/t9109-git-svn-svk-mirrorpaths.sh b/t/t9109-git-svn-svk-mirrorpaths.sh index 0e0ba3df39..7e42151851 100755 --- a/t/t9109-git-svn-svk-mirrorpaths.sh +++ b/t/t9109-git-svn-svk-mirrorpaths.sh @@ -55,11 +55,13 @@ test_expect_success 'initialize repo' " svn up && echo random untested feature >> mirror/foobar/trunk/readme && + poke mirror/foobar/trunk/readme && svn commit -m 'add a c00l feature to trunk' && svn ps -r 5 --revprop svm:headrev $uuid:3 $svnrepo && svn up && echo bug fix >> mirror/foobar/branches/silly/readme && + poke mirror/foobar/branches/silly/readme && svn commit -m 'fix a bug' && svn ps -r 6 --revprop svm:headrev $uuid:4 $svnrepo && From e98671e5c2067a39759c8f08b79c260a9f0b2771 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 02:21:19 -0800 Subject: [PATCH 152/201] git-svn: hopefully make 'fetch' more user-friendly multi-fetch is deprecated, "fetch -a" is easier to type By default, fetch will fetch everything from its default [svn-remote]; if fetch [--all|-a] is specified, then it will fetch from all svn remotes. Refspecs on the command-line (like git-fetch) are not supported. Also, enable -r/--revision arguments for fetch so users can shoot themselves in the foot^W^W^W^W^W skip some history and do the equivalent of a shallow clone/fetch they're not interested in. Signed-off-by: Eric Wong --- git-svn.perl | 58 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index fb2c864a39..3eed62fc0b 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -53,7 +53,7 @@ BEGIN my ($_stdin, $_help, $_edit, $_message, $_file, $_template, $_shared, - $_version, + $_version, $_fetch_all, $_merge, $_strategy, $_dry_run, $_prefix); $Git::SVN::_follow_parent = 1; @@ -84,7 +84,9 @@ BEGIN my %cmd = ( fetch => [ \&cmd_fetch, "Download new revisions from SVN", - { 'revision|r=s' => \$_revision, %fc_opts } ], + { 'revision|r=s' => \$_revision, + 'all|a' => \$_fetch_all, + %fc_opts } ], init => [ \&cmd_init, "Initialize a repo for tracking" . " (requires URL argument)", \%init_opts ], @@ -106,8 +108,8 @@ BEGIN 'prefix=s' => \$_prefix, } ], 'multi-fetch' => [ \&cmd_multi_fetch, - 'Fetch multiple trees (like git-svnimport)', - \%fc_opts ], + "Deprecated alias for $0 fetch --all", + { 'revision|r=s' => \$_revision, %fc_opts } ], 'migrate' => [ sub { }, # no-op, we automatically run this anyways, 'Migrate configuration/metadata/layout from @@ -226,16 +228,19 @@ sub cmd_init { } sub cmd_fetch { - if (@_) { - die "Additional fetch arguments are no longer supported.\n", - "Use --follow-parent if you have moved/copied directories - instead.\n"; + if (grep /^\d+=./, @_) { + die "'=' fetch arguments are ", + "no longer supported.\n"; } - my $gs = Git::SVN->new; - $gs->fetch(parse_revision_argument()); - if ($gs->{last_commit} && !verify_ref('refs/heads/master^0')) { - command_noisy(qw(update-ref refs/heads/master), - $gs->{last_commit}); + my ($remote) = @_; + if (@_ > 1) { + die "Usage: $0 fetch [--all|-a] [svn-remote]\n"; + } + $remote ||= $Git::SVN::default_repo_id; + if ($_fetch_all) { + cmd_multi_fetch(); + } else { + Git::SVN::fetch_all($remote, Git::SVN::read_all_remotes()); } } @@ -440,18 +445,6 @@ sub cmd_commit_diff { ########################### utility functions ######################### -sub parse_revision_argument { - if (!defined $_revision || $_revision eq 'BASE:HEAD') { - return (undef, undef); - } - return ($1, $2) if ($_revision =~ /^(\d+):(\d+)$/); - return ($_revision, $_revision) if ($_revision =~ /^\d+$/); - return (undef, $1) if ($_revision =~ /^BASE:(\d+)$/); - return ($1, undef) if ($_revision =~ /^(\d+):HEAD$/); - die "revision argument: $_revision not understood by git-svn\n", - "Try using the command-line svn client instead\n"; -} - sub complete_svn_url { my ($url, $path) = @_; $path =~ s#/+$##; @@ -755,6 +748,19 @@ sub resolve_local_globs { } } +sub parse_revision_argument { + my ($base, $head) = @_; + if (!defined $::_revision || $::_revision eq 'BASE:HEAD') { + return ($base, $head); + } + return ($1, $2) if ($::_revision =~ /^(\d+):(\d+)$/); + return ($::_revision, $::_revision) if ($::_revision =~ /^\d+$/); + return ($head, $head) if ($::_revision eq 'HEAD'); + return ($base, $1) if ($::_revision =~ /^BASE:(\d+)$/); + return ($1, $head) if ($::_revision =~ /^(\d+):HEAD$/); + die "revision argument: $::_revision not understood by git-svn\n"; +} + sub fetch_all { my ($repo_id, $remotes) = @_; my $remote = $remotes->{$repo_id}; @@ -787,6 +793,8 @@ sub fetch_all { push @gs, $gs; } } + + ($base, $head) = parse_revision_argument($base, $head); $ra->gs_fetch_loop_common($base, $head, \@gs, \@globs); } From dadc6d2a0904e55ac5a5a810dffac4d44fff0b66 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 12:27:41 -0800 Subject: [PATCH 153/201] git-svn: allow 'init' to act as multi-init multi-init is now just an alias that requires -T/-t/-b; all options that 'init' can now accept. This will hopefully simplify usage and reduce typing. Also, allow the --shared option in 'init' to take an optional argument now that 'git-init --shared' supports an optional argument. Signed-off-by: Eric Wong --- git-svn.perl | 52 ++++++++++++++++++------------ t/t9107-git-svn-migrate.sh | 4 +-- t/t9109-git-svn-svk-mirrorpaths.sh | 4 +-- 3 files changed, 36 insertions(+), 24 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 3eed62fc0b..b2931cd5aa 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -71,10 +71,10 @@ BEGIN %remote_opts ); my ($_trunk, $_tags, $_branches); -my %multi_opts = ( 'trunk|T=s' => \$_trunk, - 'tags|t=s' => \$_tags, - 'branches|b=s' => \$_branches ); -my %init_opts = ( 'template=s' => \$_template, 'shared' => \$_shared ); +my %init_opts = ( 'template=s' => \$_template, 'shared:s' => \$_shared, + 'trunk|T=s' => \$_trunk, 'tags|t=s' => \$_tags, + 'branches|b=s' => \$_branches, 'prefix=s' => \$_prefix, + %remote_opts ); my %cmt_opts = ( 'edit|e' => \$_edit, 'rmdir' => \$SVN::Git::Editor::_rmdir, 'find-copies-harder' => \$SVN::Git::Editor::_find_copies_harder, @@ -90,6 +90,10 @@ BEGIN init => [ \&cmd_init, "Initialize a repo for tracking" . " (requires URL argument)", \%init_opts ], + 'multi-init' => [ \&cmd_multi_init, + "Deprecated alias for ". + "'$0 init -T -b -t'", + \%init_opts ], dcommit => [ \&cmd_dcommit, 'Commit several diffs to merge with upstream', { 'merge|m|M' => \$_merge, @@ -101,12 +105,6 @@ BEGIN { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ], 'show-ignore' => [ \&cmd_show_ignore, "Show svn:ignore listings", { 'revision|r=i' => \$_revision } ], - 'multi-init' => [ \&cmd_multi_init, - 'Initialize multiple trees (like git-svnimport)', - { %multi_opts, %init_opts, %remote_opts, - 'revision|r=i' => \$_revision, - 'prefix=s' => \$_prefix, - } ], 'multi-fetch' => [ \&cmd_multi_fetch, "Deprecated alias for $0 fetch --all", { 'revision|r=s' => \$_revision, %fc_opts } ], @@ -182,6 +180,7 @@ sub usage { next if $cmd && $cmd ne $_; print $fd ' ',pack('A17',$_),$cmd{$_}->[1],"\n"; foreach (keys %{$cmd{$_}->[2]}) { + next if /^multi-/; # don't show deprecated commands # prints out arguments as they should be passed: my $x = s#[:=]s$## ? '' : s#[:=]i$## ? '' : ''; print $fd ' ' x 21, join(', ', map { length $_ > 1 ? @@ -207,21 +206,31 @@ sub do_git_init_db { unless (-d $ENV{GIT_DIR}) { my @init_db = ('init'); push @init_db, "--template=$_template" if defined $_template; - push @init_db, "--shared" if defined $_shared; + if (defined $_shared) { + if ($_shared =~ /[a-z]/) { + push @init_db, "--shared=$_shared"; + } else { + push @init_db, "--shared"; + } + } command_noisy(@init_db); } } +sub init_subdir { + my $repo_path = shift or return; + mkpath([$repo_path]) unless -d $repo_path; + chdir $repo_path or die "Couldn't chdir to $repo_path: $!\n"; + $ENV{GIT_DIR} = $repo_path . "/.git"; +} + sub cmd_init { - my $url = shift or die "SVN repository location required " . - "as a command-line argument\n"; - if (my $repo_path = shift) { - unless (-d $repo_path) { - mkpath([$repo_path]); - } - chdir $repo_path or croak $!; - $ENV{GIT_DIR} = $repo_path . "/.git"; + if (defined $_trunk || defined $_branches || defined $_tags) { + return cmd_multi_init(@_); } + my $url = shift or die "SVN repository location required ", + "as a command-line argument\n"; + init_subdir(@_); do_git_init_db(); Git::SVN->init($url); @@ -367,7 +376,10 @@ sub cmd_multi_init { } do_git_init_db(); $_prefix = '' unless defined $_prefix; - $url =~ s#/+$## if defined $url; + if (defined $url) { + $url =~ s#/+$##; + init_subdir(@_); + } if (defined $_trunk) { my $trunk_ref = $_prefix . 'trunk'; # try both old-style and new-style lookups: diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index d26c355f05..a20038b670 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -40,7 +40,7 @@ test_expect_success 'initialize old-style (v0) git-svn layout' " " test_expect_success 'initialize a multi-repository repo' " - git-svn multi-init $svnrepo -T trunk -t tags -b branches && + git-svn init $svnrepo -T trunk -t tags -b branches && git-config --get-all svn-remote.svn.fetch > fetch.out && grep '^trunk:refs/remotes/trunk$' fetch.out && test -n \"\`git-config --get svn-remote.svn.branches \ @@ -72,7 +72,7 @@ test_expect_success 'multi-fetch works on partial urls + paths' " refs/remotes/\$j\`\" ||exit 1; done; done " -test_expect_success 'migrate --minimize on old multi-inited layout' " +test_expect_success 'migrate --minimize on old inited layout' " git config --unset-all svn-remote.svn.fetch && git config --unset-all svn-remote.svn.url && rm -rf $GIT_DIR/svn && diff --git a/t/t9109-git-svn-svk-mirrorpaths.sh b/t/t9109-git-svn-svk-mirrorpaths.sh index 7e42151851..1e1b97b5fc 100755 --- a/t/t9109-git-svn-svk-mirrorpaths.sh +++ b/t/t9109-git-svn-svk-mirrorpaths.sh @@ -73,8 +73,8 @@ test_expect_success 'initialize repo' " cd .. " -test_expect_success 'multi-init an SVK mirror path' " - git-svn multi-init -T trunk -t tags -b branches $svnrepo/mirror/foobar +test_expect_success 'init an SVK mirror path' " + git-svn init -T trunk -t tags -b branches $svnrepo/mirror/foobar " test_expect_success 'multi-fetch an SVK mirror path' "git-svn multi-fetch" From 28710f74ea1f1d8a46c867ddd471dae3d7c3a664 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 13:32:21 -0800 Subject: [PATCH 154/201] git-svn: brown paper bag fixes * avoid skipping modification-only changes in fetch * correctly fetch when we only have branches and tags to glob from (no fetch keys defined) Signed-off-by: Eric Wong --- git-svn.perl | 5 +++-- t/t9108-git-svn-glob.sh | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index b2931cd5aa..24ca3087d6 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -782,7 +782,7 @@ sub fetch_all { my $ra = Git::SVN::Ra->new($url); my $uuid = $ra->get_uuid; my $head = $ra->get_latest_revnum; - my $base = $head; + my $base = defined $fetch ? $head : 0; # read the max revs for wildcard expansion (branches/*, tags/*) foreach my $t (qw/branches tags/) { @@ -2901,7 +2901,8 @@ sub match_globs { } } foreach (keys %$paths) { - if (/$g->{path}->{left_regex}/) { + if (/$g->{path}->{left_regex}/ && + !/$g->{path}->{regex}/) { next if $paths->{$_}->{action} !~ /^[AR]$/; get_dir_check($self, $exists, $g, $r); } diff --git a/t/t9108-git-svn-glob.sh b/t/t9108-git-svn-glob.sh index be21fc13b7..db4344cc84 100755 --- a/t/t9108-git-svn-glob.sh +++ b/t/t9108-git-svn-glob.sh @@ -55,4 +55,32 @@ test_expect_success 'test refspec globbing' " \"\`git rev-parse refs/remotes/trunk\`\" " +echo try to try > expect.two +echo nothing to see here >> expect.two +cat expect.end >> expect.two + +test_expect_success 'test left-hand-side only globbing' " + git config --add svn-remote.two.url $svnrepo && + git config --add svn-remote.two.fetch trunk:refs/remotes/two/trunk && + git config --add svn-remote.two.branches \ + 'branches/*:refs/remotes/two/branches/*' && + git config --add svn-remote.two.tags \ + 'tags/*:refs/remotes/two/tags/*' && + cd tmp && + echo 'try try' >> tags/end/src/b/readme && + poke tags/end/src/b/readme && + svn commit -m 'try to try' + cd .. && + git-svn fetch two && + test \`git rev-list refs/remotes/two/tags/end | wc -l\` -eq 6 && + test \`git rev-list refs/remotes/two/branches/start | wc -l\` -eq 3 && + test \`git rev-parse refs/remotes/two/branches/start~2\` = \ + \`git rev-parse refs/remotes/two/trunk\` && + test \`git rev-parse refs/remotes/two/tags/end~3\` = \ + \`git rev-parse refs/remotes/two/branches/start\` && + git log --pretty=oneline refs/remotes/two/tags/end | \ + sed -e 's/^.\{41\}//' > output.two && + cmp expect.two output.two + " + test_done From b4d57e5ea3349a6bf63c3626f6fb36bac11be3c0 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 15:10:44 -0800 Subject: [PATCH 155/201] git-svn: simplify the (multi-)init methods of fetching Also, some changes to avoid creating dead dirs under .git/svn/. We now create all directories as late as possible. Signed-off-by: Eric Wong --- git-svn.perl | 86 +++++++++++++++++++++------------------------------- 1 file changed, 35 insertions(+), 51 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 24ca3087d6..f4573ed102 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -487,48 +487,24 @@ sub complete_url_ls_init { "and a separate URL is not specified\n"); } } - my $r = defined $_revision ? $_revision : $ra->get_latest_revnum; - my ($dirent, undef, undef) = $ra->get_dir($repo_path, $r); my $url = $ra->{url}; - my $remote_id; - my $remote_path; - foreach my $d (sort keys %$dirent) { - next if ($dirent->{$d}->kind != $SVN::Node::dir); - my $path = "$repo_path/$d"; - my $ref = "$pfx$d"; - my $gs = eval { Git::SVN->new($ref) }; - # don't try to init already existing refs - unless ($gs) { - print "init $url/$path => $ref\n"; - $gs = Git::SVN->init($url, $path, undef, $ref, 1); - } - if ($gs) { - my $k = "svn-remote.$gs->{repo_id}.url"; - my $orig_url = eval { - command_oneline(qw/config --get/, $k) - }; - if ($orig_url && ($orig_url ne $gs->{url})) { - die "$k already set: $orig_url\n", - "wanted to set to: $gs->{url}\n"; - } - unless ($orig_url) { - command_oneline('config', $k, $gs->{url}); - } - $remote_id = $gs->{repo_id}; - last; - } + my $gs = Git::SVN->init($url, undef, undef, undef, 1); + my $k = "svn-remote.$gs->{repo_id}.url"; + my $orig_url = eval { command_oneline(qw/config --get/, $k) }; + if ($orig_url && ($orig_url ne $gs->{url})) { + die "$k already set: $orig_url\n", + "wanted to set to: $gs->{url}\n"; } - if (defined $remote_id) { - $remote_path = "$ra->{svn_path}/$repo_path/*"; - $remote_path =~ s#/+#/#g; - $remote_path =~ s#^/##g; - my ($n) = ($switch =~ /^--(\w+)/); - if (length $pfx && $pfx !~ m#/$#) { - die "--prefix='$pfx' must have a trailing slash '/'\n"; - } - command_noisy('config', "svn-remote.$remote_id.$n", - "$remote_path:refs/remotes/$pfx*"); + command_oneline('config', $k, $gs->{url}) unless $orig_url; + my $remote_path = "$ra->{svn_path}/$repo_path/*"; + $remote_path =~ s#/+#/#g; + $remote_path =~ s#^/##g; + my ($n) = ($switch =~ /^--(\w+)/); + if (length $pfx && $pfx !~ m#/$#) { + die "--prefix='$pfx' must have a trailing slash '/'\n"; } + command_noisy('config', "svn-remote.$gs->{repo_id}.$n", + "$remote_path:refs/remotes/$pfx*"); } sub verify_ref { @@ -1236,19 +1212,23 @@ sub get_fetch_range { sub tmp_config { my (@args) = @_; my $config = "$ENV{GIT_DIR}/svn/config"; - unless (-f $config) { - open my $fh, '>', $config or - die "Can't open $config: $!\n"; - print $fh "; This file is used internally by git-svn\n" or - die "Couldn't write to $config: $!\n"; - print $fh "; You should not have to edit it\n" or - die "Couldn't write to $config: $!\n"; - close $fh or die "Couldn't close $config: $!\n"; - } my $old_config = $ENV{GIT_CONFIG}; $ENV{GIT_CONFIG} = $config; $@ = undef; - my @ret = eval { command('config', @args) }; + my @ret = eval { + unless (-f $config) { + mkfile($config); + open my $fh, '>', $config or + die "Can't open $config: $!\n"; + print $fh "; This file is used internally by ", + "git-svn\n" or die + "Couldn't write to $config: $!\n"; + print $fh "; You should not have to edit it\n" or + die "Couldn't write to $config: $!\n"; + close $fh or die "Couldn't close $config: $!\n"; + } + command('config', @args); + }; my $err = $@; if (defined $old_config) { $ENV{GIT_CONFIG} = $old_config; @@ -1264,7 +1244,11 @@ sub tmp_index_do { my $old_index = $ENV{GIT_INDEX_FILE}; $ENV{GIT_INDEX_FILE} = $self->{index}; $@ = undef; - my @ret = eval { &$sub }; + my @ret = eval { + my ($dir, $base) = ($self->{index} =~ m#^(.*?)/?([^/]+)$#); + mkpath([$dir]) unless -d $dir; + &$sub; + }; my $err = $@; if (defined $old_index) { $ENV{GIT_INDEX_FILE} = $old_index; @@ -1846,7 +1830,7 @@ sub _new { $_[1] = $repo_id = sanitize_remote_name($repo_id); my $dir = "$ENV{GIT_DIR}/svn/$ref_id"; $_[3] = $path = '' unless (defined $path); - mkpath([$dir]); + mkpath(["$ENV{GIT_DIR}/svn"]); bless { ref_id => $ref_id, dir => $dir, index => "$dir/index", path => $path, config => "$ENV{GIT_DIR}/svn/config", From 6af1db447b10c03db4c04a55000efaa9aad38caa Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 16:04:10 -0800 Subject: [PATCH 156/201] git-svn: allow --log-window-size to be specified, default to 100 The newer default value should should lower memory usage for large fetches and also help with fetching from less reliable servers. Previously the value was 1000 and memory usage got a bit high on some repositories and fetching became less reliable in some cases. Signed-off-by: Eric Wong --- git-svn.perl | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index f4573ed102..8a80f81add 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -12,6 +12,7 @@ $ENV{GIT_DIR} ||= '.git'; $Git::SVN::default_repo_id = 'svn'; $Git::SVN::default_ref_id = $ENV{GIT_SVN_ID} || 'git-svn'; +$Git::SVN::Ra::_log_window_size = 100; $Git::SVN::Log::TZ = $ENV{TZ}; $ENV{TZ} = 'UTC'; @@ -65,6 +66,7 @@ BEGIN 'repack:i' => \$Git::SVN::_repack, 'noMetadata' => \$Git::SVN::_no_metadata, 'useSvmProps' => \$Git::SVN::_use_svm_props, + 'log-window-size=i' => \$Git::SVN::Ra::_log_window_size, 'quiet|q' => \$_q, 'repack-flags|repack-args|repack-opts=s' => \$Git::SVN::_repack_flags, @@ -2583,7 +2585,7 @@ sub apply_diff { } package Git::SVN::Ra; -use vars qw/@ISA $config_dir/; +use vars qw/@ISA $config_dir $_log_window_size/; use strict; use warnings; my ($can_do_switch); @@ -2747,7 +2749,7 @@ sub gs_do_switch { sub gs_fetch_loop_common { my ($self, $base, $head, $gsv, $globs) = @_; return if ($base > $head); - my $inc = 1000; + my $inc = $_log_window_size; my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc); my %common; my $common_max = scalar @$gsv; @@ -2954,6 +2956,9 @@ sub skip_unknown_revs { # 175007 - http(s):// (this repo required authorization, too...) # More codes may be discovered later... if ($errno == 175007 || $errno == 175002 || $errno == 160013) { + warn "W: Ignoring error from SVN, path probably ", + "does not exist: ($errno): ", + $err->expanded_message,"\n"; return; } die "Error from SVN, ($errno): ", $err->expanded_message,"\n"; From e8d120bd5a7e09b24c6fa2245cf429e3411028ee Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 16:29:52 -0800 Subject: [PATCH 157/201] git-svn: remember to check for clean indices on globbed refs, too Also, warn about dirty indices and avoid an unncessary write-tree call if the index is clean. Signed-off-by: Eric Wong --- git-svn.perl | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 8a80f81add..ace31021e7 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1269,10 +1269,11 @@ sub assert_index_clean { my $x = command_oneline('write-tree'); my ($y) = (command(qw/cat-file commit/, $treeish) =~ /^tree ($::sha1)/mo); - if ($y ne $x) { - unlink $self->{index} or croak $!; - command_noisy('read-tree', $treeish); - } + return if $y eq $x; + + warn "Index mismatch: $y != $x\nrereading $treeish\n"; + unlink $self->{index} or die "unlink $self->{index}: $!\n"; + command_noisy('read-tree', $treeish); $x = command_oneline('write-tree'); if ($y ne $x) { ::fatal "trees ($treeish) $y != $x\n", @@ -2755,9 +2756,6 @@ sub gs_fetch_loop_common { my $common_max = scalar @$gsv; foreach my $gs (@$gsv) { - if (my $last_commit = $gs->last_commit) { - $gs->assert_index_clean($last_commit); - } my @tmp = split m#/#, $gs->{path}; my $p = ''; foreach (@tmp) { @@ -2833,6 +2831,9 @@ sub gs_fetch_loop_common { } next unless $gs->match_paths($paths, $r); $gs->{logged_rev_props} = $logged; + if (my $last_commit = $gs->last_commit) { + $gs->assert_index_clean($last_commit); + } my $log_entry = $gs->do_fetch($paths, $r); if ($log_entry) { $gs->do_git_commit($log_entry); From 7447b4bc837d2f73fb4cd34f2a44a0cb120c5c39 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 18:38:46 -0800 Subject: [PATCH 158/201] git-svn: error checking for invalid [svn-remote "..."] sections We don't end up trying to pass an undef URL over to SVN::Ra->new because it'll segfault. Signed-off-by: Eric Wong --- git-svn.perl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index ace31021e7..201418e09c 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -753,9 +753,10 @@ sub parse_revision_argument { sub fetch_all { my ($repo_id, $remotes) = @_; - my $remote = $remotes->{$repo_id}; + my $remote = $remotes->{$repo_id} or + die "[svn-remote \"$repo_id\"] unknown\n"; my $fetch = $remote->{fetch}; - my $url = $remote->{url}; + my $url = $remote->{url} or die "svn-remote.$repo_id.url not defined\n"; my (@gs, @globs); my $ra = Git::SVN::Ra->new($url); my $uuid = $ra->get_uuid; From 60d9c97adf96533e4f02d3fc2fecec998104e8ea Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 18:47:16 -0800 Subject: [PATCH 159/201] git-svn: allow dcommit for those who only fetch from SVM with useSvmProps This allows users to use SVM (SVN::Mirror) to mirror a remote repository to use dcommit to commit to the repository that SVM was mirroring. When dcommit is used in this manner, the automatic fetch + rebase/reset does not happen; in which case the user will have to manually invoke svm/svk, run 'git svn fetch', and finally 'git rebase'. Signed-off-by: Eric Wong --- git-svn.perl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 201418e09c..bfe5d6b97e 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -309,8 +309,7 @@ sub cmd_dcommit { die "Unable to determine upstream SVN information from ", "$head history:\n $ctx\n"; } - my $gs = Git::SVN->find_by_url($url) or - die "Can't determine fetch information for $url\n"; + my $gs = Git::SVN->find_by_url($url); my $last_rev; foreach my $d (@refs) { if (!verify_ref("$d~1")) { @@ -345,6 +344,13 @@ sub cmd_dcommit { } } return if $_dry_run; + unless ($gs) { + warn "Could not determine fetch information for $url\n", + "Will not attempt to fetch and rebase commits.\n", + "This probably means you have useSvmProps and should\n", + "now resync your SVN::Mirror repository.\n"; + return; + } $gs->fetch; # we always want to rebase against the current HEAD, not any # head that was passed to us From a836a0e1729d1758b4085cd07fc79cb9acb64908 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 19:34:56 -0800 Subject: [PATCH 160/201] git-svn: documentation updates for new functionality Force the showing of the --minimize flag as an option in the 'migrate' help. Also, fix the usage function to correctly filter out the deprecated aliases. Signed-off-by: Eric Wong --- Documentation/git-svn.txt | 202 +++++++++++++++----------------------- git-svn.perl | 5 +- 2 files changed, 84 insertions(+), 123 deletions(-) diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index d45283a53f..ba3f7ce6f1 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -13,14 +13,13 @@ DESCRIPTION ----------- git-svn is a simple conduit for changesets between Subversion and git. It is not to be confused with gitlink:git-svnimport[1], which is -read-only and geared towards tracking multiple branches. +read-only. git-svn was originally designed for an individual developer who wants a bidirectional flow of changesets between a single branch in Subversion and an arbitrary number of branches in git. Since its inception, git-svn has gained the ability to track multiple branches in a manner -similar to git-svnimport; but it cannot (yet) automatically detect new -branches and tags like git-svnimport does. +similar to git-svnimport. git-svn is especially useful when it comes to tracking repositories not organized in the way Subversion developers recommend (trunk, @@ -31,23 +30,40 @@ COMMANDS -- 'init':: - Creates an empty git repository with additional metadata - directories for git-svn. The Subversion URL must be specified - as a command-line argument. Optionally, the target directory - to operate on can be specified as a second argument. Normally - this command initializes the current directory. + Initializes an empty git repository with additional + metadata directories for git-svn. The Subversion URL + may be specified as a command-line argument, or as full + URL arguments to -T/-t/-b. Optionally, the target + directory to operate on can be specified as a second + argument. Normally this command initializes the current + directory. + +-T:: +--trunk=:: +-t:: +--tags=:: +-b:: +--branches=:: + These are optional command-line options for init. Each of + these flags can point to a relative repository path + (--tags=project/tags') or a full url + (--tags=https://foo.org/project/tags) + +--prefix= + This allows one to specify a prefix which is prepended + to the names of remotes if trunk/branches/tags are + specified. The prefix does not automatically include a + trailing slash, so be sure you include one in the + argument if that is what you want. This is useful if + you wish to track multiple projects that share a common + repository. 'fetch':: -Fetch unfetched revisions from the Subversion URL we are -tracking. refs/remotes/git-svn will be updated to the -latest revision. - -Note: You should never attempt to modify the remotes/git-svn -branch outside of git-svn. Instead, create a branch from -remotes/git-svn and work on that branch. Use the 'dcommit' -command (see below) to write git commits back to -remotes/git-svn. + Fetch unfetched revisions from the Subversion remote we are + tracking. The name of the [svn-remote "..."] section in the + .git/config file may be specified as an optional command-line + argument. 'dcommit':: Commit each diff from a specified head directly to the SVN @@ -109,53 +125,13 @@ remotes/git-svn. repository (that has been init-ed with git-svn). The -r option is required for this. -'graft-branches':: - This command attempts to detect merges/branches from already - imported history. Techniques used currently include regexes, - file copies, and tree-matches). This command generates (or - modifies) the $GIT_DIR/info/grafts file. This command is - considered experimental, and inherently flawed because - merge-tracking in SVN is inherently flawed and inconsistent - across different repositories. - -'multi-init':: - This command supports git-svnimport-like command-line syntax for - importing repositories that are laid out as recommended by the - SVN folks. This is a bit more tolerant than the git-svnimport - command-line syntax and doesn't require the user to figure out - where the repository URL ends and where the repository path - begins. - --T:: ---trunk=:: --t:: ---tags=:: --b:: ---branches=:: - These are the command-line options for multi-init. Each of - these flags can point to a relative repository path - (--tags=project/tags') or a full url - (--tags=https://foo.org/project/tags) - ---prefix= - This allows one to specify a prefix which is prepended to the - names of remotes. The prefix does not automatically include a - trailing slash, so be sure you include one in the argument if - that is what you want. This is useful if you wish to track - multiple projects that share a common repository. - -'multi-fetch':: - This runs fetch on all known SVN branches we're tracking. This - will NOT discover new branches (unlike git-svnimport), so - multi-init will need to be re-run (it's idempotent). - -- OPTIONS ------- -- ---shared:: +--shared[={false|true|umask|group|all|world|everybody}]:: --template=:: Only used with the 'init' command. These are passed directly to gitlink:git-init[1]. @@ -163,14 +139,15 @@ OPTIONS -r :: --revision :: -Only used with the 'fetch' command. +Used with the 'fetch' command. -Takes any valid -r svn would accept and passes it -directly to svn. -r: ranges and "{" DATE "}" syntax -is also supported. This is passed directly to svn, see svn -documentation for more details. +This allows revision ranges for partial/cauterized history +to be supported. $NUMBER, $NUMBER1:$NUMBER2 (numeric ranges), +$NUMBER:HEAD, and BASE:$NUMBER are all supported. -This can allow you to make partial mirrors when running fetch. +This can allow you to make partial mirrors when running fetch; +but is generally not recommended because history will be skipped +and lost. -:: --stdin:: @@ -276,36 +253,19 @@ ADVANCED OPTIONS ---------------- -- --b:: ---branch :: -Used with 'fetch', 'dcommit' or 'set-tree'. - -This can be used to join arbitrary git branches to remotes/git-svn -on new commits where the tree object is equivalent. - -When used with different GIT_SVN_ID values, tags and branches in -SVN can be tracked this way, as can some merges where the heads -end up having completely equivalent content. This can even be -used to track branches across multiple SVN _repositories_. - -This option may be specified multiple times, once for each -branch. - -config key: svn.branch - -i:: --id :: -This sets GIT_SVN_ID (instead of using the environment). See the -section on -'<>' -for more information on using GIT_SVN_ID. +This sets GIT_SVN_ID (instead of using the environment). This +allows the user to override the default refname to fetch from +when tracking a single URL. The 'log' and 'dcommit' commands +no longer require this switch as an argument. -R:: --svn-remote :: Specify the [svn-remote ""] section to use, - this allows multiple repositories to be tracked. - Default: git-svn + this allows SVN multiple repositories to be tracked. + Default: "svn" --follow-parent:: This is especially helpful when we're tracking a directory @@ -369,26 +329,21 @@ Tracking and contributing to a the trunk of a Subversion-managed project: Tracking and contributing to an entire Subversion-managed project (complete with a trunk, tags and branches): -See also: -'<>' ------------------------------------------------------------------------ # Initialize a repo (like git init): - git-svn multi-init http://svn.foo.org/project \ - -T trunk -b branches -t tags + git-svn init http://svn.foo.org/project -T trunk -b branches -t tags # Fetch remote revisions: - git-svn multi-fetch + git-svn fetch # Create your own branch of trunk to hack on: git checkout -b my-trunk remotes/trunk # Do some work, and then commit your new changes to SVN, as well as # automatically updating your working HEAD: - git-svn dcommit -i trunk + git-svn dcommit # Something has been committed to trunk, rebase the latest into your branch: - git-svn multi-fetch && git rebase remotes/trunk + git-svn fetch && git rebase remotes/trunk # Append svn:ignore settings of trunk to the default git exclude file: git-svn show-ignore -i trunk >> .git/info/exclude -# Check for new branches and tags (no arguments are needed): - git-svn multi-init ------------------------------------------------------------------------ REBASE VS. PULL/MERGE @@ -411,31 +366,9 @@ DESIGN PHILOSOPHY Merge tracking in Subversion is lacking and doing branched development with Subversion is cumbersome as a result. git-svn does not do automated merge/branch tracking by default and leaves it entirely up to -the user on the git side. - -[[tracking-multiple-repos]] -TRACKING MULTIPLE REPOSITORIES OR BRANCHES ------------------------------------------- -Because git-svn does not care about relationships between different -branches or directories in a Subversion repository, git-svn has a simple -hack to allow it to track an arbitrary number of related _or_ unrelated -SVN repositories via one git repository. Simply use the --id/-i flag or -set the GIT_SVN_ID environment variable to a name other other than -"git-svn" (the default) and git-svn will ignore the contents of the -$GIT_DIR/svn/git-svn directory and instead do all of its work in -$GIT_DIR/svn/$GIT_SVN_ID for that invocation. The interface branch will -be remotes/$GIT_SVN_ID, instead of remotes/git-svn. Any -remotes/$GIT_SVN_ID branch should never be modified by the user outside -of git-svn commands. - -If you're tracking a directory that has moved, or otherwise been -branched or tagged off of another directory in the repository and you -care about the full history of the project, then you can use -the --follow-parent option. - ------------------------------------------------- - git-svn fetch --follow-parent ------------------------------------------------- +the user on the git side. git-svn does however follow copy +history of the directory that it is tracking, however (much like +how 'svn log' works). BUGS ---- @@ -452,6 +385,33 @@ 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. +CONFIGURATION +------------- + +git-svn stores [svn-remote] configuration information in the +repository .git/config file. It is similar the core git +[remote] sections except 'fetch' keys do not accept glob +arguments; but they are instead handled by the 'branches' +and 'tags' keys. Since some SVN repositories are oddly +configured with multiple projects glob expansions such those +listed below are allowed: + +------------------------------------------------------------------------ +[svn-remote "project-a"] + url = http://server.org/svn + branches = branches/*/project-a:refs/remotes/project-a/branches/* + tags = tags/*/project-a:refs/remotes/project-a/tags/* + trunk = trunk/project-a:refs/remotes/project-a/trunk +------------------------------------------------------------------------ + +Keep in mind that the '*' (asterisk) wildcard of the local ref +(left of the ':') *must* be the farthest right path component; +however the remote wildcard may be anywhere as long as it's own +independent path componet (surrounded by '/' or EOL). This +type of configuration is not automatically created by 'init' and +should be manually entered with a text-editor or using +gitlink:git-config[1] + SEE ALSO -------- gitlink:git-rebase[1] diff --git a/git-svn.perl b/git-svn.perl index bfe5d6b97e..31e536c72f 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -114,7 +114,8 @@ BEGIN # no-op, we automatically run this anyways, 'Migrate configuration/metadata/layout from previous versions of git-svn', - \%remote_opts ], + { 'minimize' => \$Git::SVN::Migration::_minimize, + %remote_opts } ], 'log' => [ \&Git::SVN::Log::cmd_show_log, 'Show commit logs', { 'limit=i' => \$Git::SVN::Log::limit, 'revision|r=s' => \$_revision, @@ -180,9 +181,9 @@ sub usage { foreach (sort keys %cmd) { next if $cmd && $cmd ne $_; + next if /^multi-/; # don't show deprecated commands print $fd ' ',pack('A17',$_),$cmd{$_}->[1],"\n"; foreach (keys %{$cmd{$_}->[2]}) { - next if /^multi-/; # don't show deprecated commands # prints out arguments as they should be passed: my $x = s#[:=]s$## ? '' : s#[:=]i$## ? '' : ''; print $fd ' ' x 21, join(', ', map { length $_ > 1 ? From 488a63ec233ce7edc5fd70172c196628b1d6a82b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 15 Feb 2007 00:40:42 -0800 Subject: [PATCH 161/201] git-svn: add support for --stat in the log command Signed-off-by: Eric Wong --- git-svn.perl | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 31e536c72f..2152bf3de8 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -3234,7 +3234,7 @@ sub show_commit_normal { print "\n"; } - foreach my $x (qw/raw diff/) { + foreach my $x (qw/raw stat diff/) { if ($c->{$x}) { print "\n"; print $_ foreach @{$c->{$x}} @@ -3266,7 +3266,7 @@ sub cmd_show_log { @args = (git_svn_log_cmd($r_min, $r_max, @args), @args); my $log = command_output_pipe(@args); run_pager(); - my (@k, $c, $d); + my (@k, $c, $d, $stat); my $esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/; while (<$log>) { if (/^${esc_color}commit ($::sha1_short)/o) { @@ -3294,6 +3294,13 @@ sub cmd_show_log { push @{$c->{diff}}, $_; } elsif ($d) { push @{$c->{diff}}, $_; + } elsif (/^\ .+\ \|\s*\d+\ $esc_color[\+\-]* + $esc_color*[\+\-]*$esc_color$/x) { + $stat = 1; + push @{$c->{stat}}, $_; + } elsif ($stat && /^ \d+ files changed, \d+ insertions/) { + push @{$c->{stat}}, $_; + $stat = undef; } elsif (/^${esc_color} (git-svn-id:.+)$/o) { ($c->{url}, $c->{r}, undef) = ::extract_metadata($1); } elsif (s/^${esc_color} //o) { From 1e889ef36c45b5554f7e317493ed3f4f901f8d9f Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 16 Feb 2007 01:45:13 -0800 Subject: [PATCH 162/201] git-svn: checkout files on new fetches On newly-created repositories, 'refs/heads/master' does not point to anything. This can be confusing to new users; so we update 'master' to point to the last imported ref after fetching is done. Once 'master' is valid; we assume HEAD points to it; and if the repository is not bare, then checkout the files if the working tree is clean and unused. Signed-off-by: Eric Wong --- git-svn.perl | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 2152bf3de8..7ffbf64139 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -56,7 +56,7 @@ BEGIN $_template, $_shared, $_version, $_fetch_all, $_merge, $_strategy, $_dry_run, - $_prefix); + $_prefix, $_no_checkout); $Git::SVN::_follow_parent = 1; my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username, 'config-dir=s' => \$Git::SVN::Ra::config_dir, @@ -67,6 +67,7 @@ BEGIN 'noMetadata' => \$Git::SVN::_no_metadata, 'useSvmProps' => \$Git::SVN::_use_svm_props, 'log-window-size=i' => \$Git::SVN::Ra::_log_window_size, + 'no-checkout' => \$_no_checkout, 'quiet|q' => \$_q, 'repack-flags|repack-args|repack-opts=s' => \$Git::SVN::_repack_flags, @@ -167,6 +168,7 @@ BEGIN $cmd{$cmd}->[0]->(@ARGV); }; fatal $@ if $@; +post_fetch_checkout(); exit 0; ####################### primary functions ###################### @@ -466,6 +468,27 @@ sub cmd_commit_diff { ########################### utility functions ######################### +sub post_fetch_checkout { + return if $_no_checkout; + my $gs = $Git::SVN::_head or return; + return if verify_ref('refs/heads/master^0'); + + my $valid_head = verify_ref('HEAD^0'); + command_noisy(qw(update-ref refs/heads/master), $gs->refname); + return if ($valid_head || !verify_ref('HEAD^0')); + + return if $ENV{GIT_DIR} !~ m#^(?:.*/)?\.git$#; + my $index = $ENV{GIT_INDEX_FILE} || "$ENV{GIT_DIR}/index"; + return if -f $index; + + chomp(my $bare = `git config --bool --get core.bare`); + return if $bare eq 'true'; + return if command_oneline(qw/rev-parse --is-inside-git-dir/) eq 'true'; + command_noisy(qw/read-tree -m -u -v HEAD HEAD/); + print STDERR "Checked out HEAD:\n ", + $gs->full_url, " r", $gs->last_rev, "\n"; +} + sub complete_svn_url { my ($url, $path) = @_; $path =~ s#/+$##; @@ -668,7 +691,7 @@ package Git::SVN; use strict; use warnings; use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent - $_repack $_repack_flags $_use_svm_props/; + $_repack $_repack_flags $_use_svm_props $_head/; use Carp qw/croak/; use File::Path qw/mkpath/; use File::Copy qw/copy/; @@ -1781,6 +1804,7 @@ sub rev_db_set { } close $fh or croak $!; if ($update_ref) { + $_head = $self; command_noisy('update-ref', '-m', "r$rev", $self->refname, $commit); } From 905f8b7dfc2c520b91f418ab0f2aecb1c371fbe4 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 16 Feb 2007 03:22:40 -0800 Subject: [PATCH 163/201] git-svn: add a 'rebase' command This works similarly to 'svn update' or 'git pull' except that it preserves linear history with 'git rebase' instead of 'git merge' for ease of dcommit-ing with git-svn. While we're at it, put the working_head_info() logic into its own function and allow --fetch-all/--all for dcommit and rebase (which will fetch all refs in the current [svn-remote] instead of just the working one). Note that the '-a' switch (short for --fetch-all/--all) has been removed as it conflicts with the non-svn 'git fetch' Signed-off-by: Eric Wong --- git-svn.perl | 91 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 63 insertions(+), 28 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 7ffbf64139..eca08bdd3b 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -56,7 +56,7 @@ BEGIN $_template, $_shared, $_version, $_fetch_all, $_merge, $_strategy, $_dry_run, - $_prefix, $_no_checkout); + $_prefix, $_no_checkout, $_verbose); $Git::SVN::_follow_parent = 1; my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username, 'config-dir=s' => \$Git::SVN::Ra::config_dir, @@ -88,7 +88,7 @@ BEGIN my %cmd = ( fetch => [ \&cmd_fetch, "Download new revisions from SVN", { 'revision|r=s' => \$_revision, - 'all|a' => \$_fetch_all, + 'fetch-all|all' => \$_fetch_all, %fc_opts } ], init => [ \&cmd_init, "Initialize a repo for tracking" . " (requires URL argument)", @@ -101,7 +101,9 @@ BEGIN 'Commit several diffs to merge with upstream', { 'merge|m|M' => \$_merge, 'strategy|s=s' => \$_strategy, + 'verbose|v' => \$_verbose, 'dry-run|n' => \$_dry_run, + 'fetch-all|all' => \$_fetch_all, %cmt_opts, %fc_opts } ], 'set-tree' => [ \&cmd_set_tree, "Set an SVN repository to a git tree-ish", @@ -129,6 +131,12 @@ BEGIN 'color' => \$Git::SVN::Log::color, 'pager=s' => \$Git::SVN::Log::pager, } ], + 'rebase' => [ \&cmd_rebase, "Fetch and rebase your working directory", + { 'merge|m|M' => \$_merge, + 'verbose|v' => \$_verbose, + 'strategy|s=s' => \$_strategy, + 'fetch-all|all' => \$_fetch_all, + %fc_opts } ], 'commit-diff' => [ \&cmd_commit_diff, 'Commit a diff between two trees', { 'message|m=s' => \$_message, @@ -248,7 +256,7 @@ sub cmd_fetch { } my ($remote) = @_; if (@_ > 1) { - die "Usage: $0 fetch [--all|-a] [svn-remote]\n"; + die "Usage: $0 fetch [--all] [svn-remote]\n"; } $remote ||= $Git::SVN::default_repo_id; if ($_fetch_all) { @@ -296,21 +304,12 @@ sub cmd_set_tree { sub cmd_dcommit { my $head = shift; $head ||= 'HEAD'; - my ($url, $rev, $uuid); - my ($fh, $ctx) = command_output_pipe('rev-list', $head); my @refs; - my $c; - while (<$fh>) { - $c = $_; - chomp $c; - ($url, $rev, $uuid) = cmt_metadata($c); - last if (defined $url && defined $rev && defined $uuid); - unshift @refs, $c; - } - close $fh; # most likely breaking the pipe + my ($url, $rev, $uuid) = working_head_info($head, \@refs); + my $c = $refs[-1]; unless (defined $url && defined $rev && defined $uuid) { die "Unable to determine upstream SVN information from ", - "$head history:\n $ctx\n"; + "$head history\n"; } my $gs = Git::SVN->find_by_url($url); my $last_rev; @@ -354,15 +353,13 @@ sub cmd_dcommit { "now resync your SVN::Mirror repository.\n"; return; } - $gs->fetch; + $_fetch_all ? $gs->fetch_all : $gs->fetch; # we always want to rebase against the current HEAD, not any # head that was passed to us my @diff = command('diff-tree', 'HEAD', $gs->refname, '--'); my @finish; if (@diff) { - @finish = qw/rebase/; - push @finish, qw/--merge/ if $_merge; - push @finish, "--strategy=$_strategy" if $_strategy; + @finish = rebase_cmd(); print STDERR "W: HEAD and ", $gs->refname, " differ, ", "using @finish:\n", "@diff"; } else { @@ -374,6 +371,24 @@ sub cmd_dcommit { command_noisy(@finish, $gs->refname); } +sub cmd_rebase { + command_noisy(qw/update-index --refresh/); + my $url = (working_head_info('HEAD'))[0]; + if (!defined $url) { + die "Unable to determine upstream SVN information from ", + "working tree history\n"; + } + + my $gs = Git::SVN->find_by_url($url); + if (command(qw/diff-index HEAD --/)) { + print STDERR "Cannot rebase with uncommited changes:\n"; + command_noisy('status'); + exit 1; + } + $_fetch_all ? $gs->fetch_all : $gs->fetch; + command_noisy(rebase_cmd(), $gs->refname); +} + sub cmd_show_ignore { my $gs = Git::SVN->new; my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum); @@ -468,6 +483,14 @@ sub cmd_commit_diff { ########################### utility functions ######################### +sub rebase_cmd { + my @cmd = qw/rebase/; + push @cmd, '-v' if $_verbose; + push @cmd, qw/--merge/ if $_merge; + push @cmd, "--strategy=$_strategy" if $_strategy; + @cmd; +} + sub post_fetch_checkout { return if $_no_checkout; my $gs = $Git::SVN::_head or return; @@ -687,6 +710,20 @@ sub cmt_metadata { command(qw/cat-file commit/, shift)))[-1]); } +sub working_head_info { + my ($head, $refs) = @_; + my ($url, $rev, $uuid); + my ($fh, $ctx) = command_output_pipe('rev-list', $head); + while (<$fh>) { + chomp; + ($url, $rev, $uuid) = cmt_metadata($_); + last if (defined $url && defined $rev && defined $uuid); + unshift @$refs, $_ if $refs; + } + close $fh; # break the pipe + ($url, $rev, $uuid); +} + package Git::SVN; use strict; use warnings; @@ -783,6 +820,12 @@ sub parse_revision_argument { sub fetch_all { my ($repo_id, $remotes) = @_; + if (ref $repo_id) { + my $gs = $repo_id; + $repo_id = undef; + $repo_id = $gs->{repo_id}; + } + $remotes ||= read_all_remotes(); my $remote = $remotes->{$repo_id} or die "[svn-remote \"$repo_id\"] unknown\n"; my $fetch = $remote->{fetch}; @@ -3085,15 +3128,7 @@ sub git_svn_log_cmd { last; } - my $url; - my ($fh, $ctx) = command_output_pipe('rev-list', $head); - while (<$fh>) { - chomp; - $url = (::cmt_metadata($_))[0]; - last if defined $url; - } - close $fh; # break the pipe - + my $url = (::working_head_info($head))[0]; my $gs = Git::SVN->find_by_url($url) || Git::SVN->_new; my @cmd = (qw/log --abbrev-commit --pretty=raw --default/, $gs->refname); From d6d3346babaf19864ea104b0140279e62f32f7e3 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 16 Feb 2007 04:05:33 -0800 Subject: [PATCH 164/201] git-svn: fix some issues for people migrating from older versions * Fixed logic for renaming old .rev_db -> .rev_db.$uuid * correctly handle manual migrations for those who decide to start use globbing to handle branches/tags over individual 'fetch' keys Signed-off-by: Eric Wong --- git-svn.perl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index eca08bdd3b..d7fc9aad52 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -844,6 +844,8 @@ sub fetch_all { "svn-remote.$repo_id.${t}-maxRev") }; if (defined $max_rev && ($max_rev < $base)) { $base = $max_rev; + } elsif (!defined $max_rev) { + $base = 0; } } @@ -1066,10 +1068,7 @@ sub new { $self->{url} = command_oneline('config', '--get', "svn-remote.$repo_id.url") or die "Failed to read \"svn-remote.$repo_id.url\" in config\n"; - if ((-z $self->db_path || ! -e $self->db_path) && - ::verify_ref($self->refname.'^0')) { - $self->rebuild; - } + $self->rebuild; $self; } @@ -1737,6 +1736,8 @@ sub set_tree { sub rebuild { my ($self) = @_; my $db_path = $self->db_path; + return if (-e $db_path && ! -z $db_path); + return unless ::verify_ref($self->refname.'^0'); if (-f $self->{db_root}) { rename $self->{db_root}, $db_path or die "rename $self->{db_root} => $db_path failed: $!\n"; @@ -1863,6 +1864,7 @@ sub rev_db_set { sub rev_db_max { my ($self) = @_; + $self->rebuild; my $db_path = $self->db_path; my @stat = stat $db_path or return 0; ($stat[7] % 41) == 0 or die "$db_path inconsistent size: $stat[7]\n"; From b7e5348c7f6369554813207a24cf841f48bd8b23 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 16 Feb 2007 04:09:28 -0800 Subject: [PATCH 165/201] git-svn: hide the private git-svn 'config' file as '.metadata' Having it named as 'config' prevents us from tracking a ref named 'config', which is a huge mistake. On the non-technical side, the word 'config' implies that a user can freely modify it; but that's not the case here. Signed-off-by: Eric Wong --- git-svn.perl | 7 ++++++- t/t9107-git-svn-migrate.sh | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/git-svn.perl b/git-svn.perl index d7fc9aad52..571259fd09 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1286,7 +1286,12 @@ sub get_fetch_range { sub tmp_config { my (@args) = @_; - my $config = "$ENV{GIT_DIR}/svn/config"; + my $old_def_config = "$ENV{GIT_DIR}/svn/config"; + my $config = "$ENV{GIT_DIR}/svn/.metadata"; + if (-e $old_def_config && ! -e $config) { + rename $old_def_config, $config or + die "Failed rename $old_def_config => $config: $!\n"; + } my $old_config = $ENV{GIT_CONFIG}; $ENV{GIT_CONFIG} = $config; $@ = undef; diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index a20038b670..dc2afdaa45 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -17,6 +17,7 @@ test_expect_success 'setup old-looking metadata' " git-svn init $svnrepo && git-svn fetch && mv $GIT_DIR/svn/* $GIT_DIR/ && + mv $GIT_DIR/svn/.metadata $GIT_DIR/ && rmdir $GIT_DIR/svn && git-update-ref refs/heads/git-svn-HEAD refs/remotes/git-svn && git-update-ref refs/heads/svn-HEAD refs/remotes/git-svn && From 0425ea90889f967c3966ace3e5a85b9a5a44c358 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 16 Feb 2007 18:45:01 -0800 Subject: [PATCH 166/201] git-svn: add 'clone' command, an alias for init + fetch Signed-off-by: Eric Wong --- git-svn.perl | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/git-svn.perl b/git-svn.perl index 571259fd09..2cc7c33381 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -90,6 +90,9 @@ BEGIN { 'revision|r=s' => \$_revision, 'fetch-all|all' => \$_fetch_all, %fc_opts } ], + clone => [ \&cmd_clone, "Initialize and fetch revisions", + { 'revision|r=s' => \$_revision, + %fc_opts, %init_opts } ], init => [ \&cmd_init, "Initialize a repo for tracking" . " (requires URL argument)", \%init_opts ], @@ -167,7 +170,7 @@ BEGIN version() if $_version; usage(1) unless defined $cmd; load_authors() if $_authors; -unless ($cmd =~ /^(?:init|multi-init|commit-diff)$/) { +unless ($cmd =~ /^(?:clone|init|multi-init|commit-diff)$/) { Git::SVN::Migration::migration_check(); } Git::SVN::init_vars(); @@ -237,6 +240,22 @@ sub init_subdir { $ENV{GIT_DIR} = $repo_path . "/.git"; } +sub cmd_clone { + my ($url, $path) = @_; + if (!defined $path && + (defined $_trunk || defined $_branches || defined $_tags) && + $url !~ m#^[a-z\+]+://#) { + $path = $url; + } + warn "--path: $path\n" if defined $path; + $path = basename($url) if !defined $path || !length $path; + warn "++path: $path\n" if defined $path; + mkpath([$path]); + chdir $path or die "Couldn't chdir to $path\n"; + cmd_init(@_); + Git::SVN::fetch_all($Git::SVN::default_repo_id); +} + sub cmd_init { if (defined $_trunk || defined $_branches || defined $_tags) { return cmd_multi_init(@_); From aea736cc6db64219b946adb4ca77f5d17bc7ab77 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 16 Feb 2007 19:15:21 -0800 Subject: [PATCH 167/201] git-svn: allow overriding of the SVN repo root in metadata This feature allows users to create repositories from alternate URLs. For example, an administrator could run git-svn on the server locally (accessing via file://) but wish to distribute the repository with a public http:// or svn:// URL in the metadata so users of it will see the public URL. Config key: svn-remote..rewriteRoot Signed-off-by: Eric Wong --- git-svn.perl | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/git-svn.perl b/git-svn.perl index 2cc7c33381..3e48c56d7e 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1406,6 +1406,26 @@ sub get_commit_parents { @ret; } +sub rewrite_root { + my ($self) = @_; + return $self->{-rewrite_root} if exists $self->{-rewrite_root}; + my $k = "svn-remote.$self->{repo_id}.rewriteRoot"; + my $rwr = eval { command_oneline(qw/config --get/, $k) }; + if ($rwr) { + $rwr =~ s#/+$##; + if ($rwr !~ m#^[a-z\+]+://#) { + die "$rwr is not a valid URL (key: $k)\n"; + } + } + $self->{-rewrite_root} = $rwr; +} + +sub metadata_url { + my ($self) = @_; + ($self->rewrite_root || $self->{url}) . + (length $self->{path} ? '/' . $self->{path} : ''); +} + sub full_url { my ($self) = @_; $self->{url} . (length $self->{path} ? '/' . $self->{path} : ''); @@ -1704,6 +1724,10 @@ sub make_log_entry { my ($name, $email) = defined $::users{$author} ? @{$::users{$author}} : ($author, undef); if (defined $headrev && $self->use_svm_props) { + if ($self->rewrite_root) { + die "Can't have both 'useSvmProps' and 'rewriteRoot' ", + "options set!\n"; + } my ($uuid, $r) = $headrev =~ m{^([a-f\d\-]{30,}):(\d+)$}; if ($uuid ne $self->{svm}->{uuid}) { die "UUID mismatch on SVM path:\n", @@ -1716,7 +1740,7 @@ sub make_log_entry { $log_entry{svm_revision} = $r; $email ||= "$author\@$uuid" } else { - $log_entry{metadata} = $self->full_url . "\@$rev " . + $log_entry{metadata} = $self->metadata_url. "\@$rev " . $self->ra->get_uuid; $email ||= "$author\@" . $self->ra->get_uuid; } From 62e349d235ecbb20c5338de5d4cbff9ce5c6aa66 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 16 Feb 2007 19:57:29 -0800 Subject: [PATCH 168/201] git-svn: add support for using svnsync properties This is similar to useSvmProps, but far simpler in implementation because svnsync retains a 1:1 between revision numbers and relative paths within the repository Config keys: svn.useSvnsyncProps svn-remote..useSvnsyncProps Signed-off-by: Eric Wong --- git-svn.perl | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 3e48c56d7e..7563eea352 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -66,6 +66,7 @@ BEGIN 'repack:i' => \$Git::SVN::_repack, 'noMetadata' => \$Git::SVN::_no_metadata, 'useSvmProps' => \$Git::SVN::_use_svm_props, + 'useSvnsyncProps' => \$Git::SVN::_use_svnsync_props, 'log-window-size=i' => \$Git::SVN::Ra::_log_window_size, 'no-checkout' => \$_no_checkout, 'quiet|q' => \$_q, @@ -747,7 +748,8 @@ package Git::SVN; use strict; use warnings; use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent - $_repack $_repack_flags $_use_svm_props $_head/; + $_repack $_repack_flags $_use_svm_props $_head + $_use_svnsync_props/; use Carp qw/croak/; use File::Path qw/mkpath/; use File::Copy qw/copy/; @@ -768,7 +770,8 @@ BEGIN # per [svn-remote "..."] section. Command-line options will *NOT* # override options set in an [svn-remote "..."] section my $e; - foreach (qw/follow_parent no_metadata use_svm_props/) { + foreach (qw/follow_parent no_metadata use_svm_props + use_svnsync_props/) { my $key = $_; $key =~ tr/_//d; $e .= "sub $_ { @@ -1186,6 +1189,50 @@ sub _set_svm_vars { Git::SVN::Ra->new($self->{url}); } +sub svnsync { + my ($self) = @_; + return $self->{svnsync} if $self->{svnsync}; + + if ($self->no_metadata) { + die "Can't have both 'noMetadata' and ", + "'useSvnsyncProps' options set!\n"; + } + if ($self->rewrite_root) { + die "Can't have both 'useSvnsyncProps' and 'rewriteRoot' ", + "options set!\n"; + } + + my $svnsync; + # see if we have it in our config, first: + eval { + my $section = "svn-remote.$self->{repo_id}"; + $svnsync = { + url => tmp_config('--get', "$section.svnsync-url"), + uuid => tmp_config('--get', "$section.svnsync-uuid"), + } + }; + if ($svnsync && $svnsync->{url} && $svnsync->{uuid}) { + return $self->{svnsync} = $svnsync; + } + + my $err = "useSvnsyncProps set, but failed to read " . + "svnsync property: svn:sync-from-"; + my $rp = $self->ra->rev_proplist(0); + + my $url = $rp->{'svn:sync-from-url'} or die $err . "url\n"; + $url =~ m{^[a-z\+]+://} or + die "doesn't look right - svn:sync-from-url is '$url'\n"; + + my $uuid = $rp->{'svn:sync-from-uuid'} or die $err . "uuid\n"; + $uuid =~ m{^[0-9a-f\-]{30,}$} or + die "doesn't look right - svn:sync-from-uuid is '$uuid'\n"; + + my $section = "svn-remote.$self->{repo_id}"; + tmp_config('--add', "$section.svnsync-uuid", $uuid); + tmp_config('--add', "$section.svnsync-url", $url); + return $self->{svnsync} = { url => $url, uuid => $uuid }; +} + # this allows us to memoize our SVN::Ra UUID locally and avoid a # remote lookup (useful for 'git svn log'). sub ra_uuid { @@ -1211,6 +1258,9 @@ sub ra { if ($self->no_metadata) { die "Can't have both 'noMetadata' and ", "'useSvmProps' options set!\n"; + } elsif ($self->use_svnsync_props) { + die "Can't have both 'useSvnsyncProps' and ", + "'useSvmProps' options set!\n"; } $ra = $self->_set_svm_vars($ra); $self->{-want_revprops} = 1; @@ -1739,6 +1789,12 @@ sub make_log_entry { $log_entry{metadata} = "$full_url\@$r $uuid"; $log_entry{svm_revision} = $r; $email ||= "$author\@$uuid" + } elsif ($self->use_svnsync_props) { + my $full_url = $self->svnsync->{url}; + $full_url .= "/$self->{path}" if length $self->{path}; + my $uuid = $self->svnsync->{uuid}; + $log_entry{metadata} = "$full_url\@$rev $uuid"; + $email ||= "$author\@$uuid" } else { $log_entry{metadata} = $self->metadata_url. "\@$rev " . $self->ra->get_uuid; From befc9adc0ced7d3e1c1316d6420007357d50b202 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 17 Feb 2007 02:53:07 -0800 Subject: [PATCH 169/201] git-svn: fix useSvmProps, hopefully for the last time svm:mirror is not useful at all for us. Parts of the old unit test were broken and based on my misunderstanding of the svm:mirror property. When we read svm:source; make sure we correctly handle the '!' in it: it is used to separate the path of the repository root from the virtual path within the repository. We don't need to make that distinction, honestly! We also ensure that subdirectories are also mirrored with the correct URL if we're using useSvmProps. We have a new test that uses dumped repo that was really created using SVN::Mirror to avoid ambiguities and mis-understandings about the svm: properties. Note: trailing whitespace in the svm.dump file is unfortunately a reality and required by SVN; so please ignore it when applying this patch. Also, ensure that the -R/--remote/--svn-remote flag is always in effect if explicitly passed via the command-line. This allows us to track logically different mirrors sharing the same URL (probably common with SVN::Mirror/SVK users). Signed-off-by: Eric Wong --- git-svn.perl | 91 +++-- t/t9109-git-svn-svk-mirrorpaths.sh | 106 ------ t/t9110-git-svn-use-svm-props.sh | 51 +++ t/t9110/svm.dump | 511 +++++++++++++++++++++++++++++ 4 files changed, 619 insertions(+), 140 deletions(-) delete mode 100755 t/t9109-git-svn-svk-mirrorpaths.sh create mode 100755 t/t9110-git-svn-use-svm-props.sh create mode 100644 t/t9110/svm.dump diff --git a/git-svn.perl b/git-svn.perl index 7563eea352..1bcf058ef6 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -164,7 +164,9 @@ BEGIN my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version, 'minimize-connections' => \$Git::SVN::Migration::_minimize, 'id|i=s' => \$Git::SVN::default_ref_id, - 'svn-remote|remote|R=s' => \$Git::SVN::default_repo_id); + 'svn-remote|remote|R=s' => sub { + $Git::SVN::no_reuse_existing = 1; + $Git::SVN::default_repo_id = $_[1] }); exit 1 if (!$rv && $cmd ne 'log'); usage(0) if $_help; @@ -749,7 +751,7 @@ package Git::SVN; use warnings; use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent $_repack $_repack_flags $_use_svm_props $_head - $_use_svnsync_props/; + $_use_svnsync_props $no_reuse_existing/; use Carp qw/croak/; use File::Path qw/mkpath/; use File::Copy qw/copy/; @@ -944,6 +946,7 @@ sub sanitize_remote_name { sub find_existing_remote { my ($url, $remotes) = @_; + return undef if $no_reuse_existing; my $existing; foreach my $repo_id (keys %$remotes) { my $u = $remotes->{$repo_id}->{url} or next; @@ -1116,9 +1119,12 @@ sub svm { $svm = { source => tmp_config('--get', "$section.svm-source"), uuid => tmp_config('--get', "$section.svm-uuid"), + replace => tmp_config('--get', "$section.svm-replace"), } }; - $self->{svm} = $svm if ($svm && $svm->{source} && $svm->{uuid}); + if ($svm && $svm->{source} && $svm->{uuid} && $svm->{replace}) { + $self->{svm} = $svm; + } $self->{svm}; } @@ -1127,64 +1133,76 @@ sub _set_svm_vars { return $ra if $self->svm; my @err = ( "useSvmProps set, but failed to read SVM properties\n", - "(svm:source, svm:mirror, svm:mirror) ", + "(svm:source, svm:uuid) ", "from the following URLs:\n" ); sub read_svm_props { - my ($self, $props) = @_; + my ($self, $ra, $path, $r) = @_; + my $props = ($ra->get_dir($path, $r))[2]; my $src = $props->{'svm:source'}; - my $mirror = $props->{'svm:mirror'}; my $uuid = $props->{'svm:uuid'}; - return undef if (!$src || !$mirror || !$uuid); + return undef if (!$src || !$uuid); - chomp($src, $mirror, $uuid); + chomp($src, $uuid); $uuid =~ m{^[0-9a-f\-]{30,}$} or die "doesn't look right - svm:uuid is '$uuid'\n"; - # don't know what a '!' is there for, also the - # username is of no interest - $src =~ s{/?!$}{$mirror}; + + # the '!' is used to mark the repos_root!/relative/path + $src =~ s{/?!/?}{/}; $src =~ s{/+$}{}; # no trailing slashes please + # username is of no interest $src =~ s{(^[a-z\+]*://)[^/@]*@}{$1}; + my $replace = $ra->{url}; + $replace .= "/$path" if length $path; + my $section = "svn-remote.$self->{repo_id}"; - tmp_config('--add', "$section.svm-source", $src); - tmp_config('--add', "$section.svm-uuid", $uuid); - $self->{svm} = { source => $src , uuid => $uuid }; - return 1; + tmp_config("$section.svm-source", $src); + tmp_config("$section.svm-replace", $replace); + tmp_config("$section.svm-uuid", $uuid); + $self->{svm} = { + source => $src, + uuid => $uuid, + replace => $replace + }; } my $r = $ra->get_latest_revnum; my $path = $self->{path}; - my @tried_a = ($path); + my %tried; while (length $path) { - if ($self->read_svm_props(($ra->get_dir($path, $r))[2])) { - return $ra; + unless ($tried{"$self->{url}/$path"}) { + return $ra if $self->read_svm_props($ra, $path, $r); + $tried{"$self->{url}/$path"} = 1; } - $path =~ s#/?[^/]+$## && push @tried_a, $path; - } - if ($self->read_svm_props(($ra->get_dir('', $r))[2])) { - return $ra; + $path =~ s#/?[^/]+$##; } + die "Path: '$path' should be ''\n" if $path ne ''; + return $ra if $self->read_svm_props($ra, $path, $r); + $tried{"$self->{url}/$path"} = 1; if ($ra->{repos_root} eq $self->{url}) { - die @err, map { " $self->{url}/$_\n" } @tried_a, "\n"; + die @err, (map { " $_\n" } keys %tried), "\n"; } # nope, make sure we're connected to the repository root: my $ok; my @tried_b; $path = $ra->{svn_path}; - $path =~ s#/?[^/]+$##; # we already tried this one above $ra = Git::SVN::Ra->new($ra->{repos_root}); while (length $path) { - $ok = $self->read_svm_props(($ra->get_dir($path, $r))[2]); - last if $ok; - $path =~ s#/?[^/]+$## && push @tried_b, $path; + unless ($tried{"$ra->{url}/$path"}) { + $ok = $self->read_svm_props($ra, $path, $r); + last if $ok; + $tried{"$ra->{url}/$path"} = 1; + } + $path =~ s#/?[^/]+$##; } - $ok = $self->read_svm_props(($ra->get_dir('', $r))[2]) unless $ok; + die "Path: '$path' should be ''\n" if $path ne ''; + $ok ||= $self->read_svm_props($ra, $path, $r); + $tried{"$ra->{url}/$path"} = 1; if (!$ok) { - die @err, map { " $self->{url}/$_\n" } @tried_a, "\n", - map { " $ra->{url}/$_\n" } @tried_b, "\n" + die @err, (map { " $_\n" } keys %tried), "\n"; } Git::SVN::Ra->new($self->{url}); } @@ -1779,13 +1797,18 @@ sub make_log_entry { "options set!\n"; } my ($uuid, $r) = $headrev =~ m{^([a-f\d\-]{30,}):(\d+)$}; - if ($uuid ne $self->{svm}->{uuid}) { + # we don't want "SVM: initializing mirror for junk" ... + return undef if $r == 0; + my $svm = $self->svm; + if ($uuid ne $svm->{uuid}) { die "UUID mismatch on SVM path:\n", - "expected: $self->{svm}->{uuid}\n", + "expected: $svm->{uuid}\n", " got: $uuid\n"; } - my $full_url = $self->{svm}->{source}; - $full_url .= "/$self->{path}" if length $self->{path}; + my $full_url = $self->full_url; + $full_url =~ s#^\Q$svm->{replace}\E(/|$)#$svm->{source}$1# or + die "Failed to replace '$svm->{replace}' with ", + "'$svm->{source}' in $full_url\n"; $log_entry{metadata} = "$full_url\@$r $uuid"; $log_entry{svm_revision} = $r; $email ||= "$author\@$uuid" diff --git a/t/t9109-git-svn-svk-mirrorpaths.sh b/t/t9109-git-svn-svk-mirrorpaths.sh deleted file mode 100755 index 1e1b97b5fc..0000000000 --- a/t/t9109-git-svn-svk-mirrorpaths.sh +++ /dev/null @@ -1,106 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2006 Sam Vilian -# - -test_description='git-svn on SVK mirror paths' -. ./lib-git-svn.sh - -# ok, people who don't have SVK installed probably don't care about -# this test. - -# we set up the repository manually, because even if SVK is installed -# it is difficult to use it in a way that is idempotent. - -# we are not yet testing merge tickets.. - -uuid=b00bface-b1ff-c0ff-f0ff-b0bafe775e1e -url=https://really.slow.server.com/foobar - -test_expect_success 'initialize repo' " - git config svn-remote.svn.useSvmProps true && - - echo '#!/bin/sh' > $rawsvnrepo/hooks/pre-revprop-change && - echo 'exit 0' >> $rawsvnrepo/hooks/pre-revprop-change && - chmod +x $rawsvnrepo/hooks/pre-revprop-change && - - mkdir import && - cd import && - mkdir local && - echo hello > local/readme && - svn import -m 'random local work' . $svnrepo && - cd .. && - - svn co $svnrepo wc && - cd wc && - mkdir -p mirror/foobar && - svn add mirror && - svn ps svm:source $url mirror/foobar && - svn ps svm:uuid $uuid mirror/foobar && - svn ps svm:mirror / mirror/foobar && - svn commit -m 'setup mirror/foobar as mirror of upstream' && - svn ps -r 2 --revprop svm:headrev $uuid:0 $svnrepo && - - mkdir mirror/foobar/trunk - echo hello, world > mirror/foobar/trunk/readme && - svn add mirror/foobar/trunk && - svn commit -m 'first upstream revision' && - svn ps -r 3 --revprop svm:headrev $uuid:1 $svnrepo && - - svn up && - svn mkdir mirror/foobar/branches && - svn cp mirror/foobar/trunk mirror/foobar/branches/silly && - svn commit -m 'make branch for silliness' && - svn ps -r 4 --revprop svm:headrev $uuid:2 $svnrepo && - - svn up && - echo random untested feature >> mirror/foobar/trunk/readme && - poke mirror/foobar/trunk/readme && - svn commit -m 'add a c00l feature to trunk' && - svn ps -r 5 --revprop svm:headrev $uuid:3 $svnrepo && - - svn up && - echo bug fix >> mirror/foobar/branches/silly/readme && - poke mirror/foobar/branches/silly/readme && - svn commit -m 'fix a bug' && - svn ps -r 6 --revprop svm:headrev $uuid:4 $svnrepo && - - svn mkdir mirror/foobar/tags && - svn cp mirror/foobar/branches/silly mirror/foobar/tags/blah-1.0 && - svn commit -m 'make a release' && - svn ps -r 7 --revprop svm:headrev $uuid:5 $svnrepo && - - cd .. - " - -test_expect_success 'init an SVK mirror path' " - git-svn init -T trunk -t tags -b branches $svnrepo/mirror/foobar - " - -test_expect_success 'multi-fetch an SVK mirror path' "git-svn multi-fetch" - -test_expect_success 'got tag history OK' " - test \`git-log --pretty=oneline remotes/tags/blah-1.0 | wc -l\` -eq 3 - " - -test_expect_success 're-wrote git-svn-id URL, revision and UUID' " - git cat-file commit refs/remotes/trunk | \ - fgrep 'git-svn-id: $url/mirror/foobar/trunk@3 $uuid' && - git cat-file commit refs/remotes/tags/blah-1.0 | \ - fgrep 'git-svn-id: $url/mirror/foobar/tags/blah-1.0@5 $uuid' - git cat-file commit refs/remotes/silly | \ - fgrep 'git-svn-id: $url/mirror/foobar/branches/silly@4 $uuid' - " - -test_expect_success 're-wrote author e-mail domain UUID' " - test \`git log --pretty=fuller trunk | \ - grep '<.*@.*>' | fgrep '@$uuid>' | wc -l\` -eq 4 && - test \`git log --pretty=fuller remotes/silly | \ - grep '<.*@.*>' | fgrep '@$uuid>' | wc -l\` -eq 6 && - test \`git log --pretty=fuller remotes/tags/blah-1.0 | \ - grep '<.*@.*>' | fgrep '@$uuid>' | wc -l\` -eq 6 - " - -test_debug 'gitk --all &' - -test_done diff --git a/t/t9110-git-svn-use-svm-props.sh b/t/t9110-git-svn-use-svm-props.sh new file mode 100755 index 0000000000..9db0d8fd8d --- /dev/null +++ b/t/t9110-git-svn-use-svm-props.sh @@ -0,0 +1,51 @@ +#!/bin/sh +# +# Copyright (c) 2007 Eric Wong +# + +test_description='git-svn useSvmProps test' + +. ./lib-git-svn.sh + +test_expect_success 'load svm repo' " + svnadmin load -q $rawsvnrepo < ../t9110/svm.dump && + git-svn init -R arr -i bar $svnrepo/mirror/arr && + git-svn init -R argh -i dir $svnrepo/mirror/argh && + git-svn init -R argh -i e $svnrepo/mirror/argh/a/b/c/d/e && + git-config svn.useSvmProps true && + git-svn fetch --all + " + +uuid=161ce429-a9dd-4828-af4a-52023f968c89 + +bar_url=http://mayonaise/svnrepo/bar +test_expect_success 'verify metadata for /bar' " + git-cat-file commit refs/remotes/bar | \ + grep '^git-svn-id: $bar_url@12 $uuid$' && + git-cat-file commit refs/remotes/bar~1 | \ + grep '^git-svn-id: $bar_url@11 $uuid$' && + git-cat-file commit refs/remotes/bar~2 | \ + grep '^git-svn-id: $bar_url@10 $uuid$' && + git-cat-file commit refs/remotes/bar~3 | \ + grep '^git-svn-id: $bar_url@9 $uuid$' && + git-cat-file commit refs/remotes/bar~4 | \ + grep '^git-svn-id: $bar_url@6 $uuid$' && + git-cat-file commit refs/remotes/bar~5 | \ + grep '^git-svn-id: $bar_url@1 $uuid$' + " + +e_url=http://mayonaise/svnrepo/dir/a/b/c/d/e +test_expect_success 'verify metadata for /dir/a/b/c/d/e' " + git-cat-file commit refs/remotes/e | \ + grep '^git-svn-id: $e_url@1 $uuid$' + " + +dir_url=http://mayonaise/svnrepo/dir +test_expect_success 'verify metadata for /dir' " + git-cat-file commit refs/remotes/dir | \ + grep '^git-svn-id: $dir_url@2 $uuid$' && + git-cat-file commit refs/remotes/dir~1 | \ + grep '^git-svn-id: $dir_url@1 $uuid$' + " + +test_done diff --git a/t/t9110/svm.dump b/t/t9110/svm.dump new file mode 100644 index 0000000000..cc799c238d --- /dev/null +++ b/t/t9110/svm.dump @@ -0,0 +1,511 @@ +SVN-fs-dump-format-version: 2 + +UUID: de5973c6-545d-41da-aded-c265f9039e74 + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2007-02-17T06:54:59.793104Z +PROPS-END + +Revision-number: 1 +Prop-content-length: 200 +Content-length: 200 + +K 7 +svn:log +V 40 +SVM: initializing mirror for /mirror/arr +K 10 +svn:author +V 3 +svm +K 11 +svm:headrev +V 39 +161ce429-a9dd-4828-af4a-52023f968c89:0 + +K 8 +svn:date +V 27 +2007-02-17T06:55:00.121647Z +PROPS-END + +Node-path: +Node-kind: dir +Node-action: change +Prop-content-length: 44 +Content-length: 44 + +K 10 +svm:mirror +V 12 +/mirror/arr + +PROPS-END + + +Node-path: mirror +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: mirror/arr +Node-kind: dir +Node-action: add +Prop-content-length: 116 +Content-length: 116 + +K 10 +svm:source +V 29 +http://mayonaise/svnrepo!/bar +K 8 +svm:uuid +V 36 +161ce429-a9dd-4828-af4a-52023f968c89 +PROPS-END + + +Revision-number: 2 +Prop-content-length: 182 +Content-length: 182 + +K 7 +svn:log +V 18 +import for git-svn +K 10 +svn:author +V 7 +svnsync +K 11 +svm:headrev +V 39 +161ce429-a9dd-4828-af4a-52023f968c89:1 + +K 8 +svn:date +V 27 +2007-02-17T05:10:52.108847Z +PROPS-END + +Node-path: mirror/arr +Node-kind: dir +Node-action: change +Prop-content-length: 116 +Content-length: 116 + +K 10 +svm:source +V 29 +http://mayonaise/svnrepo!/bar +K 8 +svm:uuid +V 36 +161ce429-a9dd-4828-af4a-52023f968c89 +PROPS-END + + +Node-path: mirror/arr/zzz +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 4 +Text-content-md5: 33b02bc15ce9557d2dd8484d58f95ac4 +Content-length: 14 + +PROPS-END +zzz + + +Revision-number: 3 +Prop-content-length: 230 +Content-length: 230 + +K 7 +svn:log +V 66 +new symlink is added to a file that was also just made executable + +K 10 +svn:author +V 7 +svnsync +K 11 +svm:headrev +V 39 +161ce429-a9dd-4828-af4a-52023f968c89:6 + +K 8 +svn:date +V 27 +2007-02-17T05:11:01.686891Z +PROPS-END + +Node-path: mirror/arr/zzz +Node-kind: file +Node-action: change +Prop-content-length: 36 +Text-content-length: 4 +Text-content-md5: 33b02bc15ce9557d2dd8484d58f95ac4 +Content-length: 40 + +K 14 +svn:executable +V 1 +* +PROPS-END +zzz + + +Revision-number: 4 +Prop-content-length: 192 +Content-length: 192 + +K 7 +svn:log +V 28 +/bar/d should be in the log + +K 10 +svn:author +V 7 +svnsync +K 11 +svm:headrev +V 39 +161ce429-a9dd-4828-af4a-52023f968c89:9 + +K 8 +svn:date +V 27 +2007-02-17T05:11:07.686552Z +PROPS-END + +Node-path: mirror/arr/d +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 4 +Text-content-md5: 0bee89b07a248e27c83fc3d5951213c1 +Content-length: 14 + +PROPS-END +abc + + +Revision-number: 5 +Prop-content-length: 185 +Content-length: 185 + +K 7 +svn:log +V 20 +add a new directory + +K 10 +svn:author +V 7 +svnsync +K 11 +svm:headrev +V 40 +161ce429-a9dd-4828-af4a-52023f968c89:10 + +K 8 +svn:date +V 27 +2007-02-17T05:11:08.405953Z +PROPS-END + +Node-path: mirror/arr/newdir +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: mirror/arr/newdir/dir +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 4 +Text-content-md5: 9cd599a3523898e6a12e13ec787da50a +Content-length: 14 + +PROPS-END +new + + +Revision-number: 6 +Prop-content-length: 196 +Content-length: 196 + +K 7 +svn:log +V 31 +modify a file in new directory + +K 10 +svn:author +V 7 +svnsync +K 11 +svm:headrev +V 40 +161ce429-a9dd-4828-af4a-52023f968c89:11 + +K 8 +svn:date +V 27 +2007-02-17T05:11:09.126645Z +PROPS-END + +Node-path: mirror/arr/newdir/dir +Node-kind: file +Node-action: change +Text-content-length: 8 +Text-content-md5: a950e20332358e523a5e9d571e47fa64 +Content-length: 8 + +new +foo + + +Revision-number: 7 +Prop-content-length: 179 +Content-length: 179 + +K 7 +svn:log +V 14 +update /bar/d + +K 10 +svn:author +V 7 +svnsync +K 11 +svm:headrev +V 40 +161ce429-a9dd-4828-af4a-52023f968c89:12 + +K 8 +svn:date +V 27 +2007-02-17T05:11:09.846221Z +PROPS-END + +Node-path: mirror/arr/d +Node-kind: file +Node-action: change +Text-content-length: 4 +Text-content-md5: 7abb78de7f2756ca8b511cbc879fd5e7 +Content-length: 4 + +cba + + +Revision-number: 8 +Prop-content-length: 201 +Content-length: 201 + +K 7 +svn:log +V 41 +SVM: initializing mirror for /mirror/argh +K 10 +svn:author +V 3 +svm +K 11 +svm:headrev +V 39 +161ce429-a9dd-4828-af4a-52023f968c89:0 + +K 8 +svn:date +V 27 +2007-02-17T06:56:03.703677Z +PROPS-END + +Node-path: +Node-kind: dir +Node-action: change +Prop-content-length: 57 +Content-length: 57 + +K 10 +svm:mirror +V 25 +/mirror/argh +/mirror/arr + +PROPS-END + + +Node-path: mirror/argh +Node-kind: dir +Node-action: add +Prop-content-length: 116 +Content-length: 116 + +K 10 +svm:source +V 29 +http://mayonaise/svnrepo!/dir +K 8 +svm:uuid +V 36 +161ce429-a9dd-4828-af4a-52023f968c89 +PROPS-END + + +Revision-number: 9 +Prop-content-length: 182 +Content-length: 182 + +K 7 +svn:log +V 18 +import for git-svn +K 10 +svn:author +V 7 +svnsync +K 11 +svm:headrev +V 39 +161ce429-a9dd-4828-af4a-52023f968c89:1 + +K 8 +svn:date +V 27 +2007-02-17T05:10:52.108847Z +PROPS-END + +Node-path: mirror/argh +Node-kind: dir +Node-action: change +Prop-content-length: 116 +Content-length: 116 + +K 10 +svm:source +V 29 +http://mayonaise/svnrepo!/dir +K 8 +svm:uuid +V 36 +161ce429-a9dd-4828-af4a-52023f968c89 +PROPS-END + + +Node-path: mirror/argh/a +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: mirror/argh/a/b +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: mirror/argh/a/b/c +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: mirror/argh/a/b/c/d +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: mirror/argh/a/b/c/d/e +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: mirror/argh/a/b/c/d/e/file +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 9 +Text-content-md5: 3fd46fe46fcdcf062c802ca60dc826d5 +Content-length: 19 + +PROPS-END +deep dir + + +Revision-number: 10 +Prop-content-length: 197 +Content-length: 197 + +K 7 +svn:log +V 33 +try a deep --rmdir with a commit + +K 10 +svn:author +V 7 +svnsync +K 11 +svm:headrev +V 39 +161ce429-a9dd-4828-af4a-52023f968c89:2 + +K 8 +svn:date +V 27 +2007-02-17T05:10:54.847015Z +PROPS-END + +Node-path: mirror/argh/file +Node-kind: file +Node-action: add +Node-copyfrom-rev: 9 +Node-copyfrom-path: mirror/argh/a/b/c/d/e/file +Text-content-length: 9 +Text-content-md5: 3fd46fe46fcdcf062c802ca60dc826d5 +Content-length: 9 + +deep dir + + +Node-path: mirror/argh/a +Node-action: delete + + From e2b36f6018062cfa44b6952ce73c0609db0af240 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 18 Feb 2007 01:30:35 -0800 Subject: [PATCH 170/201] git-svn: add test for useSvnsyncProps These tests are very similar as the ones I used for useSvmProps and expect the same results because both dumps were generated from the same original repo. Signed-off-by: Eric Wong --- t/t9111-git-svn-use-svnsync-props.sh | 51 +++ t/t9111/svnsync.dump | 562 +++++++++++++++++++++++++++ 2 files changed, 613 insertions(+) create mode 100755 t/t9111-git-svn-use-svnsync-props.sh create mode 100644 t/t9111/svnsync.dump diff --git a/t/t9111-git-svn-use-svnsync-props.sh b/t/t9111-git-svn-use-svnsync-props.sh new file mode 100755 index 0000000000..483d7f8159 --- /dev/null +++ b/t/t9111-git-svn-use-svnsync-props.sh @@ -0,0 +1,51 @@ +#!/bin/sh +# +# Copyright (c) 2007 Eric Wong +# + +test_description='git-svn useSvnsyncProps test' + +. ./lib-git-svn.sh + +test_expect_success 'load svnsync repo' " + svnadmin load -q $rawsvnrepo < ../t9111/svnsync.dump && + git-svn init -R arr -i bar $svnrepo/bar && + git-svn init -R argh -i dir $svnrepo/dir && + git-svn init -R argh -i e $svnrepo/dir/a/b/c/d/e && + git-config svn.useSvnsyncProps true && + git-svn fetch --all + " + +uuid=161ce429-a9dd-4828-af4a-52023f968c89 + +bar_url=http://mayonaise/svnrepo/bar +test_expect_success 'verify metadata for /bar' " + git-cat-file commit refs/remotes/bar | \ + grep '^git-svn-id: $bar_url@12 $uuid$' && + git-cat-file commit refs/remotes/bar~1 | \ + grep '^git-svn-id: $bar_url@11 $uuid$' && + git-cat-file commit refs/remotes/bar~2 | \ + grep '^git-svn-id: $bar_url@10 $uuid$' && + git-cat-file commit refs/remotes/bar~3 | \ + grep '^git-svn-id: $bar_url@9 $uuid$' && + git-cat-file commit refs/remotes/bar~4 | \ + grep '^git-svn-id: $bar_url@6 $uuid$' && + git-cat-file commit refs/remotes/bar~5 | \ + grep '^git-svn-id: $bar_url@1 $uuid$' + " + +e_url=http://mayonaise/svnrepo/dir/a/b/c/d/e +test_expect_success 'verify metadata for /dir/a/b/c/d/e' " + git-cat-file commit refs/remotes/e | \ + grep '^git-svn-id: $e_url@1 $uuid$' + " + +dir_url=http://mayonaise/svnrepo/dir +test_expect_success 'verify metadata for /dir' " + git-cat-file commit refs/remotes/dir | \ + grep '^git-svn-id: $dir_url@2 $uuid$' && + git-cat-file commit refs/remotes/dir~1 | \ + grep '^git-svn-id: $dir_url@1 $uuid$' + " + +test_done diff --git a/t/t9111/svnsync.dump b/t/t9111/svnsync.dump new file mode 100644 index 0000000000..a9a46eeb29 --- /dev/null +++ b/t/t9111/svnsync.dump @@ -0,0 +1,562 @@ +SVN-fs-dump-format-version: 2 + +UUID: b4bfe35e-f256-4096-874c-08c5639ecad7 + +Revision-number: 0 +Prop-content-length: 240 +Content-length: 240 + +K 18 +svn:sync-from-uuid +V 36 +161ce429-a9dd-4828-af4a-52023f968c89 +K 10 +svn:author +V 7 +svnsync +K 24 +svn:sync-last-merged-rev +V 2 +12 +K 8 +svn:date +V 27 +2007-02-17T05:10:52.017552Z +K 17 +svn:sync-from-url +V 24 +http://mayonaise/svnrepo +PROPS-END + +Revision-number: 1 +Prop-content-length: 120 +Content-length: 120 + +K 7 +svn:log +V 18 +import for git-svn +K 10 +svn:author +V 7 +svnsync +K 8 +svn:date +V 27 +2007-02-17T05:10:52.108847Z +PROPS-END + +Node-path: bar +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: bar/zzz +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 4 +Text-content-md5: 33b02bc15ce9557d2dd8484d58f95ac4 +Content-length: 14 + +PROPS-END +zzz + + +Node-path: dir +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: dir/a +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: dir/a/b +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: dir/a/b/c +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: dir/a/b/c/d +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: dir/a/b/c/d/e +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: dir/a/b/c/d/e/file +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 9 +Text-content-md5: 3fd46fe46fcdcf062c802ca60dc826d5 +Content-length: 19 + +PROPS-END +deep dir + + +Node-path: exec.sh +Node-kind: file +Node-action: add +Prop-content-length: 35 +Text-content-length: 10 +Text-content-md5: 3e2b31c72181b87149ff995e7202c0e3 +Content-length: 45 + +K 14 +svn:executable +V 0 + +PROPS-END +#!/bin/sh + + +Node-path: foo +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 4 +Text-content-md5: d3b07384d113edec49eaa6238ad5ff00 +Content-length: 14 + +PROPS-END +foo + + +Node-path: foo.link +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 8 +Text-content-md5: 1043146e49ef02cab12eef865cb34ff3 +Content-length: 41 + +K 11 +svn:special +V 1 +* +PROPS-END +link foo + +Revision-number: 2 +Prop-content-length: 135 +Content-length: 135 + +K 7 +svn:log +V 33 +try a deep --rmdir with a commit + +K 10 +svn:author +V 7 +svnsync +K 8 +svn:date +V 27 +2007-02-17T05:10:54.847015Z +PROPS-END + +Node-path: dir/file +Node-kind: file +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: dir/a/b/c/d/e/file +Text-content-length: 9 +Text-content-md5: 3fd46fe46fcdcf062c802ca60dc826d5 +Content-length: 9 + +deep dir + + +Node-path: dir/a +Node-action: delete + + +Node-path: file +Node-kind: file +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: dir/a/b/c/d/e/file +Text-content-length: 9 +Text-content-md5: 3fd46fe46fcdcf062c802ca60dc826d5 +Content-length: 9 + +deep dir + + +Revision-number: 3 +Prop-content-length: 136 +Content-length: 136 + +K 7 +svn:log +V 34 +remove executable bit from a file + +K 10 +svn:author +V 7 +svnsync +K 8 +svn:date +V 27 +2007-02-17T05:10:58.232691Z +PROPS-END + +Node-path: exec.sh +Node-kind: file +Node-action: change +Prop-content-length: 10 +Text-content-length: 10 +Text-content-md5: 3e2b31c72181b87149ff995e7202c0e3 +Content-length: 20 + +PROPS-END +#!/bin/sh + + +Revision-number: 4 +Prop-content-length: 131 +Content-length: 131 + +K 7 +svn:log +V 29 +add executable bit back file + +K 10 +svn:author +V 7 +svnsync +K 8 +svn:date +V 27 +2007-02-17T05:10:59.666560Z +PROPS-END + +Node-path: exec.sh +Node-kind: file +Node-action: change +Prop-content-length: 36 +Text-content-length: 10 +Text-content-md5: 3e2b31c72181b87149ff995e7202c0e3 +Content-length: 46 + +K 14 +svn:executable +V 1 +* +PROPS-END +#!/bin/sh + + +Revision-number: 5 +Prop-content-length: 154 +Content-length: 154 + +K 7 +svn:log +V 52 +executable file becomes a symlink to bar/zzz (file) + +K 10 +svn:author +V 7 +svnsync +K 8 +svn:date +V 27 +2007-02-17T05:11:00.676495Z +PROPS-END + +Node-path: exec.sh +Node-kind: file +Node-action: change +Prop-content-length: 33 +Text-content-length: 12 +Text-content-md5: f138693371665cc117742508761d684d +Content-length: 45 + +K 11 +svn:special +V 1 +* +PROPS-END +link bar/zzz + +Revision-number: 6 +Prop-content-length: 168 +Content-length: 168 + +K 7 +svn:log +V 66 +new symlink is added to a file that was also just made executable + +K 10 +svn:author +V 7 +svnsync +K 8 +svn:date +V 27 +2007-02-17T05:11:01.686891Z +PROPS-END + +Node-path: bar/zzz +Node-kind: file +Node-action: change +Prop-content-length: 36 +Text-content-length: 4 +Text-content-md5: 33b02bc15ce9557d2dd8484d58f95ac4 +Content-length: 40 + +K 14 +svn:executable +V 1 +* +PROPS-END +zzz + + +Node-path: exec-2.sh +Node-kind: file +Node-action: add +Node-copyfrom-rev: 5 +Node-copyfrom-path: exec.sh +Text-content-length: 12 +Text-content-md5: f138693371665cc117742508761d684d +Content-length: 12 + +link bar/zzz + +Revision-number: 7 +Prop-content-length: 136 +Content-length: 136 + +K 7 +svn:log +V 34 +modify a symlink to become a file + +K 10 +svn:author +V 7 +svnsync +K 8 +svn:date +V 27 +2007-02-17T05:11:02.677035Z +PROPS-END + +Node-path: exec-2.sh +Node-kind: file +Node-action: change +Prop-content-length: 10 +Text-content-length: 9 +Text-content-md5: 8e92eff9e911886cede27d420f89c735 +Content-length: 19 + +PROPS-END +git help + + +Revision-number: 8 +Prop-content-length: 109 +Content-length: 109 + +K 7 +svn:log +V 8 +éï∏ + +K 10 +svn:author +V 7 +svnsync +K 8 +svn:date +V 27 +2007-02-17T05:11:03.676862Z +PROPS-END + +Node-path: exec-2.sh +Node-kind: file +Node-action: change +Text-content-length: 17 +Text-content-md5: 49881954063cf26ca48c212396a957ca +Content-length: 17 + +git help +# hello + + +Revision-number: 9 +Prop-content-length: 130 +Content-length: 130 + +K 7 +svn:log +V 28 +/bar/d should be in the log + +K 10 +svn:author +V 7 +svnsync +K 8 +svn:date +V 27 +2007-02-17T05:11:07.686552Z +PROPS-END + +Node-path: bar/d +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 4 +Text-content-md5: 0bee89b07a248e27c83fc3d5951213c1 +Content-length: 14 + +PROPS-END +abc + + +Revision-number: 10 +Prop-content-length: 122 +Content-length: 122 + +K 7 +svn:log +V 20 +add a new directory + +K 10 +svn:author +V 7 +svnsync +K 8 +svn:date +V 27 +2007-02-17T05:11:08.405953Z +PROPS-END + +Node-path: bar/newdir +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: bar/newdir/dir +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 4 +Text-content-md5: 9cd599a3523898e6a12e13ec787da50a +Content-length: 14 + +PROPS-END +new + + +Revision-number: 11 +Prop-content-length: 133 +Content-length: 133 + +K 7 +svn:log +V 31 +modify a file in new directory + +K 10 +svn:author +V 7 +svnsync +K 8 +svn:date +V 27 +2007-02-17T05:11:09.126645Z +PROPS-END + +Node-path: bar/newdir/dir +Node-kind: file +Node-action: change +Text-content-length: 8 +Text-content-md5: a950e20332358e523a5e9d571e47fa64 +Content-length: 8 + +new +foo + + +Revision-number: 12 +Prop-content-length: 116 +Content-length: 116 + +K 7 +svn:log +V 14 +update /bar/d + +K 10 +svn:author +V 7 +svnsync +K 8 +svn:date +V 27 +2007-02-17T05:11:09.846221Z +PROPS-END + +Node-path: bar/d +Node-kind: file +Node-action: change +Text-content-length: 4 +Text-content-md5: 7abb78de7f2756ca8b511cbc879fd5e7 +Content-length: 4 + +cba + + From a81ed0b63edb0d473e17e078de2269b735156a79 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 18 Feb 2007 02:10:51 -0800 Subject: [PATCH 171/201] git-svn: documentation updates This documents the 'clone' and 'rebase' commands of git-svn. Additionaly, examples are updated to use them instead of the lower-level 'init' and 'fetch' commands. Signed-off-by: Eric Wong --- Documentation/git-svn.txt | 115 +++++++++++++++++++++++++++----------- 1 file changed, 81 insertions(+), 34 deletions(-) diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index ba3f7ce6f1..bd163cfad6 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -65,6 +65,32 @@ COMMANDS .git/config file may be specified as an optional command-line argument. +'clone':: + Runs 'init' and 'fetch'. It will automatically create a + directory based on the basename of the URL passed to it; + or if a second argument is passed; it will create a directory + and work within that. It accepts all arguments that the + 'init' and 'fetch' commands accept; with the exception of + '--fetch-all'. After a repository is cloned, the 'fetch' + command will be able to update revisions without affecting + the working tree; and the 'rebase' command will be able + to update the working tree with the latest changes. + +'rebase':: + This fetches revisions from the SVN parent of the current HEAD + and rebases the current (uncommitted to SVN) work against it. + + This works similarly to 'svn update' or 'git-pull' except that + it preserves linear history with 'git-rebase' instead of + 'git-merge' for ease of dcommit-ing with git-svn. + + This accepts all options that 'git-svn fetch' and 'git-rebase' + accepts. However '--fetch-all' only fetches from the current + [svn-remote], and not all [svn-remote] definitions. + + Like 'git-rebase'; this requires that the working tree be clean + and have no uncommitted changes. + 'dcommit':: Commit each diff from a specified head directly to the SVN repository, and then rebase or reset (depending on whether or @@ -234,7 +260,7 @@ config key: svn.repackflags -s:: --strategy=:: -These are only used with the 'dcommit' command. +These are only used with the 'dcommit' and 'rebase' commands. Passed directly to git-rebase when using 'dcommit' if a 'git-reset' cannot be used (see dcommit). @@ -276,6 +302,11 @@ no longer require this switch as an argument. config key: svn.followparent +-- +CONFIG FILE-ONLY OPTIONS +------------------------ +-- + svn.noMetadata: svn-remote..noMetadata: This gets rid of the git-svn-id: lines at the end of every commit. @@ -301,8 +332,27 @@ svn-remote..useSvmProps: URL and UUID, and use it when generating metadata in commit messages. - Using this conflicts with the 'noMetadata' option for - (hopefully) obvious reasons. +svn.useSvnsyncProps: +svn-remote..useSvnsyncprops: + Similar to the useSvmProps option; this is for users + of the svnsync(1) command distributed with SVN 1.4.x and + later. + +svn-remote..rewriteRoot + This allows users to create repositories from alternate + URLs. For example, an administrator could run git-svn on the + server locally (accessing via file://) but wish to distribute + the repository with a public http:// or svn:// URL in the + metadata so users of it will see the public URL. + + +Since the noMetadata, rewriteRoot, useSvnsyncProps and useSvmProps +options all affect the metadata generated and used by git-svn; they +*must* be set in the configuration file before any history is imported +and these settings should never be changed once they are set. + +Additionally, only one of these four options can be used per-svn-remote +section because they affect the 'git-svn-id:' metadata line. -- @@ -312,17 +362,20 @@ Basic Examples Tracking and contributing to a the trunk of a Subversion-managed project: ------------------------------------------------------------------------ -# Initialize a repo (like git init): - git-svn init http://svn.foo.org/project/trunk -# Fetch remote revisions: - git-svn fetch -# Create your own branch to hack on: - git checkout -b my-branch remotes/git-svn -# Do some work, and then commit your new changes to SVN, as well as -# automatically updating your working HEAD: +# Clone a repo (like git clone): + git-svn clone http://svn.foo.org/project/trunk +# Enter the newly cloned directory: + cd trunk +# You should be on master branch, double-check with git-branch + git branch +# Do some work and commit locally to git: + git commit ... +# Something is committed to SVN, rebase your local changes against the +# latest changes in SVN: + git-svn rebase +# Now commit your changes (that were committed previously using git) to SVN, +# as well as automatically updating your working HEAD: git-svn dcommit -# Something is committed to SVN, rebase the latest into your branch: - git-svn fetch && git rebase remotes/git-svn # Append svn:ignore settings to the default git exclude file: git-svn show-ignore >> .git/info/exclude ------------------------------------------------------------------------ @@ -331,19 +384,15 @@ Tracking and contributing to an entire Subversion-managed project (complete with a trunk, tags and branches): ------------------------------------------------------------------------ -# Initialize a repo (like git init): - git-svn init http://svn.foo.org/project -T trunk -b branches -t tags -# Fetch remote revisions: - git-svn fetch -# Create your own branch of trunk to hack on: - git checkout -b my-trunk remotes/trunk -# Do some work, and then commit your new changes to SVN, as well as -# automatically updating your working HEAD: - git-svn dcommit -# Something has been committed to trunk, rebase the latest into your branch: - git-svn fetch && git rebase remotes/trunk -# Append svn:ignore settings of trunk to the default git exclude file: - git-svn show-ignore -i trunk >> .git/info/exclude +# Clone a repo (like git clone): + git-svn clone http://svn.foo.org/project -T trunk -b branches -t tags +# View all branches and tags you have cloned: + git branch -r +# Reset your master to trunk (or any other branch, replacing 'trunk' +# with the appropriate name): + git reset --hard remotes/trunk +# You may only dcommit to one branch/tag/trunk at a time. The usage +# of dcommit/rebase/show-ignore should be teh same as above. ------------------------------------------------------------------------ REBASE VS. PULL/MERGE @@ -356,7 +405,7 @@ pulled or merged from. This is because the author favored If you use 'git-svn set-tree A..B' to commit several diffs and you do not have the latest remotes/git-svn merged into my-branch, you should -use 'git rebase' to update your work branch instead of 'git pull' or +use 'git-svn rebase' to update your work branch instead of 'git pull' or 'git merge'. 'pull/merge' can cause non-linear history to be flattened when committing into SVN, which can lead to merge commits reversing previous commits in SVN. @@ -373,17 +422,15 @@ how 'svn log' works). BUGS ---- -We ignore all SVN properties except svn:executable. Too difficult to -map them since we rely heavily on git write-tree being _exactly_ the -same on both the SVN and git working trees and I prefer not to clutter -working trees with metadata files. +We ignore all SVN properties except svn:executable. Any unhandled +properties are logged to $GIT_DIR/svn//unhandled.log 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. +the possible corner cases (git doesn't do it, either). Committing +renamed and copied files are fully supported if they're similar enough +for git to detect them. CONFIGURATION ------------- From 0dfaf0a4e1905a9137d3f2f691620529aeb3b4fa Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 18 Feb 2007 02:34:09 -0800 Subject: [PATCH 172/201] git-svn: allow metadata options to be specified with 'init' and 'clone' Since the options that affect the way metadata is handled in git-svn, should be consistently set/unset throughout history imported by git-svn; it makes sense to allow the user to set certain options from the command-line that will write to the config file when initially creating the repository. Also, fix some formatting issues while we're updating documentation. Signed-off-by: Eric Wong --- Documentation/git-svn.txt | 24 ++++++++++++++++-------- git-svn.perl | 13 +++++++++++++ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index bd163cfad6..da68f6d738 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -49,6 +49,15 @@ COMMANDS (--tags=project/tags') or a full url (--tags=https://foo.org/project/tags) +--no-metadata:: + Set the 'noMetadata' option in the [svn-remote] config. +--use-svm-props:: + Set the 'useSvmProps' option in the [svn-remote] config. +--use-svnsync-props:: + Set the 'useSvnsyncProps' option in the [svn-remote] config. +--rewrite-root=:: + Set the 'rewriteRoot' option in the [svn-remote] config. + --prefix= This allows one to specify a prefix which is prepended to the names of remotes if trunk/branches/tags are @@ -307,8 +316,8 @@ CONFIG FILE-ONLY OPTIONS ------------------------ -- -svn.noMetadata: -svn-remote..noMetadata: +svn.noMetadata:: +svn-remote..noMetadata:: This gets rid of the git-svn-id: lines at the end of every commit. If you lose your .git/svn/git-svn/.rev_db file, git-svn will not @@ -319,8 +328,8 @@ svn-remote..noMetadata: this, either. Using this conflicts with the 'useSvmProps' option for (hopefully) obvious reasons. -svn.useSvmProps: -svn-remote..useSvmProps: +svn.useSvmProps:: +svn-remote..useSvmProps:: This allows git-svn to re-map repository URLs and UUIDs from mirrors created using SVN::Mirror (or svk) for metadata. @@ -332,20 +341,19 @@ svn-remote..useSvmProps: URL and UUID, and use it when generating metadata in commit messages. -svn.useSvnsyncProps: -svn-remote..useSvnsyncprops: +svn.useSvnsyncProps:: +svn-remote..useSvnsyncprops:: Similar to the useSvmProps option; this is for users of the svnsync(1) command distributed with SVN 1.4.x and later. -svn-remote..rewriteRoot +svn-remote..rewriteRoot:: This allows users to create repositories from alternate URLs. For example, an administrator could run git-svn on the server locally (accessing via file://) but wish to distribute the repository with a public http:// or svn:// URL in the metadata so users of it will see the public URL. - Since the noMetadata, rewriteRoot, useSvnsyncProps and useSvmProps options all affect the metadata generated and used by git-svn; they *must* be set in the configuration file before any history is imported diff --git a/git-svn.perl b/git-svn.perl index 1bcf058ef6..dc78dcf8cf 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -75,9 +75,14 @@ BEGIN %remote_opts ); my ($_trunk, $_tags, $_branches); +my %icv; my %init_opts = ( 'template=s' => \$_template, 'shared:s' => \$_shared, 'trunk|T=s' => \$_trunk, 'tags|t=s' => \$_tags, 'branches|b=s' => \$_branches, 'prefix=s' => \$_prefix, + 'no-metadata' => sub { $icv{noMetadata} = 1 }, + 'use-svm-props' => sub { $icv{useSvmProps} = 1 }, + 'use-svnsync-props' => sub { $icv{useSvnsyncProps} = 1 }, + 'rewrite-root=s' => sub { $icv{rewriteRoot} = $_[1] }, %remote_opts ); my %cmt_opts = ( 'edit|e' => \$_edit, 'rmdir' => \$SVN::Git::Editor::_rmdir, @@ -234,6 +239,14 @@ sub do_git_init_db { } command_noisy(@init_db); } + my $set; + my $pfx = "svn-remote.$Git::SVN::default_repo_id"; + foreach my $i (keys %icv) { + die "'$set' and '$i' cannot both be set\n" if $set; + next unless defined $icv{$i}; + command_noisy('config', "$pfx.$i", $icv{$i}); + $set = $i; + } } sub init_subdir { From 1a97a506043691741f25e8967e76123c1114d1fb Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 20 Feb 2007 00:43:19 -0800 Subject: [PATCH 173/201] git-svn: give show-ignore HEAD smarts, like dcommit and log This allows the user to run git-svn show-ignore on there current HEAD without needing to remember which branch/ref they branched from with -i. Also, find_by_url should correctly handle cases where the URL passed to it is not valid. Signed-off-by: Eric Wong --- git-svn.perl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/git-svn.perl b/git-svn.perl index dc78dcf8cf..b4e8966919 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -425,7 +425,8 @@ sub cmd_rebase { } sub cmd_show_ignore { - my $gs = Git::SVN->new; + my $url = (::working_head_info('HEAD'))[0]; + my $gs = Git::SVN->find_by_url($url) || Git::SVN->new; my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum); $gs->traverse_ignore(\*STDOUT, '', $r); } @@ -1034,6 +1035,7 @@ sub init_remote_config { sub find_by_url { # repos_root and, path are optional my ($class, $full_url, $repos_root, $path) = @_; + return undef unless defined $full_url; my $remotes = read_all_remotes(); if (defined $full_url && defined $repos_root && !defined $path) { $path = $full_url; From 5253dc33b713e3de63a25305bfc5e966999a0fbe Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 20 Feb 2007 01:36:30 -0800 Subject: [PATCH 174/201] git-svn: ensure we're at the top-level and can access $GIT_DIR If we are run inside a subdirectory of a working tree, we'll chdir to the top first before touching anything. This also prevents the accidental creation of .git directories inside subdirectories since they need metadata. Noticed by maio on #git Signed-off-by: Eric Wong --- git-svn.perl | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/git-svn.perl b/git-svn.perl index b4e8966919..a6d98f1608 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -9,6 +9,7 @@ $AUTHOR = 'Eric Wong '; $VERSION = '@@GIT_VERSION@@'; +my $git_dir_user_set = 1 if defined $ENV{GIT_DIR}; $ENV{GIT_DIR} ||= '.git'; $Git::SVN::default_repo_id = 'svn'; $Git::SVN::default_ref_id = $ENV{GIT_SVN_ID} || 'git-svn'; @@ -178,6 +179,28 @@ BEGIN version() if $_version; usage(1) unless defined $cmd; load_authors() if $_authors; + +# make sure we're always running +unless ($cmd =~ /(?:clone|init|multi-init)$/) { + unless (-d $ENV{GIT_DIR}) { + if ($git_dir_user_set) { + die "GIT_DIR=$ENV{GIT_DIR} explicitly set, ", + "but it is not a directory\n"; + } + my $git_dir = delete $ENV{GIT_DIR}; + chomp(my $cdup = command_oneline(qw/rev-parse --show-cdup/)); + unless (length $cdup) { + die "Already at toplevel, but $git_dir ", + "not found '$cdup'\n"; + } + chdir $cdup or die "Unable to chdir up to '$cdup'\n"; + unless (-d $git_dir) { + die "$git_dir still not found after going to ", + "'$cdup'\n"; + } + $ENV{GIT_DIR} = $git_dir; + } +} unless ($cmd =~ /^(?:clone|init|multi-init|commit-diff)$/) { Git::SVN::Migration::migration_check(); } From 18ea92bd818d38c808329abf77ee8f1ca156f446 Mon Sep 17 00:00:00 2001 From: Sam Vilain Date: Fri, 23 Feb 2007 12:32:29 +1300 Subject: [PATCH 175/201] git-svn: don't consider SVN URL usernames significant when comparing http://foo@blah.com/path is the same as http://blah.com/path, so remove usernames from URLs before storing them in commits, and when reading them from commits. Signed-off-by: Eric Wong --- git-svn.perl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/git-svn.perl b/git-svn.perl index a6d98f1608..ea5afb7f80 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1847,6 +1847,8 @@ sub make_log_entry { $full_url =~ s#^\Q$svm->{replace}\E(/|$)#$svm->{source}$1# or die "Failed to replace '$svm->{replace}' with ", "'$svm->{source}' in $full_url\n"; + # throw away username for storing in records + remove_username($full_url); $log_entry{metadata} = "$full_url\@$r $uuid"; $log_entry{svm_revision} = $r; $email ||= "$author\@$uuid" @@ -1915,12 +1917,14 @@ sub rebuild { my ($rev_list, $ctx) = command_output_pipe("rev-list", $self->refname); my $latest; my $full_url = $self->full_url; + remove_username($full_url); my $svn_uuid; while (<$rev_list>) { chomp; my $c = $_; die "Non-SHA1: $c\n" unless $c =~ /^$::sha1$/o; my ($url, $rev, $uuid) = ::cmt_metadata($c); + remove_username($url); # ignore merges (from set-tree) next if (!defined $rev || !$uuid); @@ -2094,6 +2098,10 @@ sub uri_encode { $f } +sub remove_username { + $_[0] =~ s{^([^:]*://)[^@]+@}{$1}; +} + package Git::SVN::Prompt; use strict; use warnings; From a0d7fe3fcd790876e510c11459df95a0d595e2ad Mon Sep 17 00:00:00 2001 From: Sam Vilain Date: Fri, 23 Feb 2007 12:32:30 +1300 Subject: [PATCH 176/201] git-svn: document --username Also, it turns out that SVN::Ra doesn't attempt to deal with authentication or pass the username to ssh when doing svn+ssh:// URLs Signed-off-by: Eric Wong --- Documentation/git-svn.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index da68f6d738..cf094ca357 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -57,6 +57,11 @@ COMMANDS Set the 'useSvnsyncProps' option in the [svn-remote] config. --rewrite-root=:: Set the 'rewriteRoot' option in the [svn-remote] config. +--username=:: + For transports that SVN handles authentication for (http, + https, and plain svn), specify the username. For other + transports (eg svn+ssh://), you must include the username in + the URL, eg svn+ssh://foo@svn.bar.com/project --prefix= This allows one to specify a prefix which is prepended From f30603fcf37f44942a2173386c0f580a508158df Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 23 Feb 2007 01:26:26 -0800 Subject: [PATCH 177/201] git-svn: fix clone when a target directory has been specified Several bugs caused this to fail: * GIT_DIR was set incorrectly after entering the target directory * Avoid double chdir-ing when clone is called with an explicit path * create target subdirectory *before* running git-init when using the multi-init path Signed-off-by: Eric Wong --- git-svn.perl | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index ea5afb7f80..a5c6eb9fec 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -276,7 +276,7 @@ sub init_subdir { my $repo_path = shift or return; mkpath([$repo_path]) unless -d $repo_path; chdir $repo_path or die "Couldn't chdir to $repo_path: $!\n"; - $ENV{GIT_DIR} = $repo_path . "/.git"; + $ENV{GIT_DIR} = '.git'; } sub cmd_clone { @@ -286,12 +286,8 @@ sub cmd_clone { $url !~ m#^[a-z\+]+://#) { $path = $url; } - warn "--path: $path\n" if defined $path; $path = basename($url) if !defined $path || !length $path; - warn "++path: $path\n" if defined $path; - mkpath([$path]); - chdir $path or die "Couldn't chdir to $path\n"; - cmd_init(@_); + cmd_init($url, $path); Git::SVN::fetch_all($Git::SVN::default_repo_id); } @@ -459,12 +455,12 @@ sub cmd_multi_init { unless (defined $_trunk || defined $_branches || defined $_tags) { usage(1); } - do_git_init_db(); $_prefix = '' unless defined $_prefix; if (defined $url) { $url =~ s#/+$##; init_subdir(@_); } + do_git_init_db(); if (defined $_trunk) { my $trunk_ref = $_prefix . 'trunk'; # try both old-style and new-style lookups: From e2c475d91cc65b110a20c9836142a4a7e608a87c Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 23 Feb 2007 01:57:40 -0800 Subject: [PATCH 178/201] git-svn: fix reconnections to different paths of svn:// repositories Clearing the pool of the previous SVN::Ra connection we have seems to to fix mysterious connection dropping errors when reconnecting to different paths of svn:// repositories hosted by rubyforge.org. Note: I'm not sure *why* this fixes things things, but it does for me. Signed-off-by: Eric Wong --- git-svn.perl | 1 + 1 file changed, 1 insertion(+) diff --git a/git-svn.perl b/git-svn.perl index a5c6eb9fec..2bd70a1577 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2879,6 +2879,7 @@ sub new { my ($class, $url) = @_; $url =~ s!/+$!!; return $RA if ($RA && $RA->{url} eq $url); + $RA->{pool}->clear if $RA; SVN::_Core::svn_config_ensure($config_dir, undef); my ($baton, $callbacks) = SVN::Core::auth_open_helper([ From 2e5e24803fc92a1da5b941495dc22f888fc612b6 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 23 Feb 2007 02:21:59 -0800 Subject: [PATCH 179/201] git-svn: fix some potential bugs with --follow-parent When using do_switch: We only need to ensure the index is clean and set to that of the parent tree) we rely on being able to reconstruct full files with deltas transferred over the network. When using do_update: We may safely unlink the index if we are fetching an entire new tree with do_update. Having an old index (from a previously deleted/abandoned directory) around can cause irrelevant files to be mistakenly kept. Signed-off-by: Eric Wong --- git-svn.perl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/git-svn.perl b/git-svn.perl index 2bd70a1577..41961b59f6 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1675,9 +1675,9 @@ sub find_parent_branch { } if (defined $r0 && defined $parent) { print STDERR "Found branch parent: ($self->{ref_id}) $parent\n"; - $self->assert_index_clean($parent); my $ed; if ($self->ra->can_do_switch) { + $self->assert_index_clean($parent); print STDERR "Following parent with do_switch\n"; # do_switch works with svn/trunk >= r22312, but that # is not included with SVN 1.4.3 (the latest version @@ -2932,6 +2932,10 @@ sub gs_do_update { my $new = ($rev_a == $rev_b); my $path = $gs->{path}; + if ($new && -e $gs->{index}) { + unlink $gs->{index} or die + "Couldn't unlink index: $gs->{index}: $!\n"; + } my $pool = SVN::Pool->new; $editor->set_path_strip($path); my (@pc) = split m#/#, $path; From 509b4d73b26bb442fd409bbca3772ba22875d310 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 23 Feb 2007 03:11:52 -0800 Subject: [PATCH 180/201] .mailmap maintenance after pulling from git-svn Signed-off-by: Junio C Hamano --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index c7a3a75f33..3a624eabc3 100644 --- a/.mailmap +++ b/.mailmap @@ -27,6 +27,7 @@ Nguyễn Thái Ngọc Duy Ramsay Allan Jones René Scharfe Robert Fitzsimons +Sam Vilain Santi Béjar Sean Estabrooks Shawn O. Pearce From 8ab40a20053aa4a0f8d92d08ece88ff09b771435 Mon Sep 17 00:00:00 2001 From: "Dmitry V. Levin" Date: Fri, 23 Feb 2007 20:12:33 +0300 Subject: [PATCH 181/201] git-show-ref --verify: Fail if called without a reference builtin-show-ref.c (cmd_show_ref): Fail if called with --verify option but without a reference. Signed-off-by: Dmitry V. Levin Signed-off-by: Junio C Hamano --- builtin-show-ref.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/builtin-show-ref.c b/builtin-show-ref.c index 853f13f6ae..75211e64f9 100644 --- a/builtin-show-ref.c +++ b/builtin-show-ref.c @@ -221,9 +221,11 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix) } if (verify) { - unsigned char sha1[20]; - + if (!pattern) + die("--verify requires a reference"); while (*pattern) { + unsigned char sha1[20]; + if (!strncmp(*pattern, "refs/", 5) && resolve_ref(*pattern, sha1, 1, NULL)) { if (!quiet) From bdd69c2f64d303f99b0f530263c3c04d329c2227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Santi=20B=C3=A9jar?= Date: Fri, 23 Feb 2007 17:03:43 +0100 Subject: [PATCH 182/201] core.legacyheaders: Use the description used in RelNotes-1.5.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It explains what it does and why, and says how to use the new format. Signed-off-by: Santi Béjar Signed-off-by: Junio C Hamano --- Documentation/config.txt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 4a22a00b71..9fec76935e 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -192,10 +192,17 @@ core.compression:: slowest. core.legacyheaders:: - A boolean which enables the legacy object header format in case - you want to interoperate with old clients accessing the object - database directly (where the "http://" and "rsync://" protocols - count as direct access). + A boolean which + changes the format of loose objects so that they are more + efficient to pack and to send out of the repository over git + native protocol, since v1.4.2. However, loose objects + written in the new format cannot be read by git older than + that version; people fetching from your repository using + older versions of git over dumb transports (e.g. http) + will also be affected. ++ +To let git use the new loose object format, you have to +set core.legacyheaders to false. core.packedGitWindowSize:: Number of bytes of a pack file to map into memory in a From c06d2daa12f72f175e3569bbb2a5f25e5f134f5e Mon Sep 17 00:00:00 2001 From: Robin Rosenberg Date: Fri, 23 Feb 2007 23:27:58 +0100 Subject: [PATCH 183/201] Limit filename for format-patch Badly formatted commits may have very long comments. This causes git-format-patch to fail. To avoid that, truncate the filename to a value we believe will always work. Err out if the patch file cannot be created. Signed-off-by: Robin Rosenberg Acked-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin-log.c | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/builtin-log.c b/builtin-log.c index af2de54371..a5e4b625f8 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -224,6 +224,9 @@ int cmd_log(int argc, const char **argv, const char *prefix) return cmd_log_walk(&rev); } +/* format-patch */ +#define FORMAT_PATCH_NAME_MAX 64 + static int istitlechar(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || @@ -264,15 +267,18 @@ static int git_format_config(const char *var, const char *value) static FILE *realstdout = NULL; static const char *output_directory = NULL; -static void reopen_stdout(struct commit *commit, int nr, int keep_subject) +static int reopen_stdout(struct commit *commit, int nr, int keep_subject) { - char filename[1024]; + char filename[PATH_MAX]; char *sol; int len = 0; - int suffix_len = strlen(fmt_patch_suffix) + 10; /* ., NUL and slop */ + int suffix_len = strlen(fmt_patch_suffix) + 1; if (output_directory) { - strlcpy(filename, output_directory, 1000); + if (strlen(output_directory) >= + sizeof(filename) - FORMAT_PATCH_NAME_MAX - suffix_len) + return error("name of output directory is too long"); + strlcpy(filename, output_directory, sizeof(filename) - suffix_len); len = strlen(filename); if (filename[len - 1] != '/') filename[len++] = '/'; @@ -297,7 +303,8 @@ static void reopen_stdout(struct commit *commit, int nr, int keep_subject) } for (j = 0; - len < sizeof(filename) - suffix_len && + j < FORMAT_PATCH_NAME_MAX - suffix_len - 5 && + len < sizeof(filename) - suffix_len && sol[j] && sol[j] != '\n'; j++) { if (istitlechar(sol[j])) { @@ -314,10 +321,16 @@ static void reopen_stdout(struct commit *commit, int nr, int keep_subject) } while (filename[len - 1] == '.' || filename[len - 1] == '-') len--; + filename[len] = 0; } + if (len + suffix_len >= sizeof(filename)) + return error("Patch pathname too long"); strcpy(filename + len, fmt_patch_suffix); fprintf(realstdout, "%s\n", filename); - freopen(filename, "w", stdout); + if (freopen(filename, "w", stdout) == NULL) + return error("Cannot open patch file %s",filename); + return 0; + } static int get_patch_id(struct commit *commit, struct diff_options *options, @@ -573,7 +586,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.message_id = message_id; } if (!use_stdout) - reopen_stdout(commit, rev.nr, keep_subject); + if (reopen_stdout(commit, rev.nr, keep_subject)) + die("Failed to create output files"); shown = log_tree_commit(&rev, commit); free(commit->buffer); commit->buffer = NULL; From b1440cc8060e79f885691ebeae7ce033bd606a7c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 24 Feb 2007 01:05:27 -0800 Subject: [PATCH 184/201] Reword git-am 3-way fallback failure message. When the blobs recorded on the index lines in the patch as pre-image blobs are not found in the repository, "git-am" punted saying that the index line does not record anything useful. This was not clear enough -- the index line does have something useful but the problem was that it was not useful in _that_ repository. Reword the message as Francis Moreau suggests. Signed-off-by: Junio C Hamano --- git-am.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-am.sh b/git-am.sh index 6db9cb503a..2c73d116b2 100755 --- a/git-am.sh +++ b/git-am.sh @@ -66,7 +66,7 @@ fall_back_3way () { git-update-index -z --index-info <"$dotest/patch-merge-index-info" && GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \ git-write-tree >"$dotest/patch-merge-base+" || - cannot_fallback "Patch does not record usable index information." + cannot_fallback "Repository lacks necessary blobs to fall back on 3-way merge." echo Using index info to reconstruct a base tree... if GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \ From 5089277718503a4de7817b5f6754cb03116d5524 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 23 Feb 2007 03:44:30 -0800 Subject: [PATCH 185/201] diff-patch: Avoid emitting double-slashes in textual patch. Signed-off-by: Junio C Hamano --- diff.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/diff.c b/diff.c index 13b9b6c560..b8a90e91a9 100644 --- a/diff.c +++ b/diff.c @@ -211,6 +211,8 @@ static void emit_rewrite_diff(const char *name_a, diff_populate_filespec(two, 0); lc_a = count_lines(one->data, one->size); lc_b = count_lines(two->data, two->size); + name_a += (*name_a == '/'); + name_b += (*name_b == '/'); printf("--- a/%s\n+++ b/%s\n@@ -", name_a, name_b); print_line_count(lc_a); printf(" +"); @@ -1020,8 +1022,8 @@ static void builtin_diff(const char *name_a, const char *set = diff_get_color(o->color_diff, DIFF_METAINFO); const char *reset = diff_get_color(o->color_diff, DIFF_RESET); - a_one = quote_two("a/", name_a); - b_two = quote_two("b/", name_b); + a_one = quote_two("a/", name_a + (*name_a == '/')); + b_two = quote_two("b/", name_b + (*name_b == '/')); lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null"; lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null"; printf("%sdiff --git %s %s%s\n", set, a_one, b_two, reset); From 92446aba47b0e0db28f7b858ea387efcca30ab44 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 25 Feb 2007 02:18:11 -0500 Subject: [PATCH 186/201] Don't modify CREDITS-FILE if it hasn't changed. We should always avoid rewriting a built file during `make install` if nothing has changed since `make all`. This is to help support the typical installation process of compiling a package as yourself, then installing it as root. Forcing CREDITS-FILE to be always be rebuilt in the Makefile means that CREDITS-GEN needs to check for a change and only update CREDITS-FILE if the file content actually differs. After all, content is king in Git. Signed-off-by: Junio C Hamano Signed-off-by: Shawn O. Pearce --- CREDITS-GEN | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/CREDITS-GEN b/CREDITS-GEN index da2c07629e..d1b0f86355 100755 --- a/CREDITS-GEN +++ b/CREDITS-GEN @@ -20,8 +20,8 @@ tree_search () generate_credits () { tip=$1 && - rm -f $CF && - git shortlog -n -s $tip | sed 's/: .*$//' >$CF || exit + rm -f "$2" && + git shortlog -n -s $tip | sed 's/: .*$//' >"$2" || exit } # Always use the tarball credits file if found, just @@ -36,10 +36,14 @@ generate_credits () # that fact. # +credits_tmp=/var/tmp/gitgui-credits-$$ +trap 'rm -f "$credits_tmp"' 0 + +orig="$credits_tmp" + if test -f credits then - rm -f $CF && - cp credits $CF || exit + orig=credits elif prefix="$(git rev-parse --show-prefix 2>/dev/null)" && test -n "$prefix" && head=$(git rev-list --max-count=1 HEAD -- . 2>/dev/null) && @@ -47,12 +51,21 @@ elif prefix="$(git rev-parse --show-prefix 2>/dev/null)" && tip=$(tree_search $head $tree) && test -n "$tip" then - generate_credits $tip || exit + generate_credits $tip "$orig" || exit elif tip="$(git rev-parse --verify HEAD 2>/dev/null)" && test -n "$tip" then - generate_credits $tip || exit + generate_credits $tip "$orig" || exit else echo "error: Cannot locate authorship information." >&2 exit 1 fi + +if test -f "$orig" && cmp -s "$orig" "$CF" +then + : noop +else + rm -f "$CF" && + cat "$orig" >"$CF" +fi + From ab242f809a9e06ebf935c0512155fc5661ab84ab Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 25 Feb 2007 01:24:34 -0800 Subject: [PATCH 187/201] rerere: do not skip two conflicted paths next to each other. The code forgot to take the for (;;) loop control into account, incrementing the index once too many. Signed-off-by: Junio C Hamano --- builtin-rerere.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin-rerere.c b/builtin-rerere.c index 318d959d89..ac0bf335a8 100644 --- a/builtin-rerere.c +++ b/builtin-rerere.c @@ -160,7 +160,7 @@ static int find_conflict(struct path_list *conflict) ce_stage(e3) == 3 && ce_same_name(e1, e2) && ce_same_name(e1, e3)) { path_list_insert((const char *)e1->name, conflict); - i += 3; + i += 2; } } return 0; From 128917274943bd0e2dea69862c5a9893a962e350 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 25 Feb 2007 01:29:43 -0800 Subject: [PATCH 188/201] rerere: do not deal with symlinks. Who would use multi-line symlinks that would benefit from rerere? Just ignore them. Signed-off-by: Junio C Hamano --- builtin-rerere.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/builtin-rerere.c b/builtin-rerere.c index ac0bf335a8..58c5fed91d 100644 --- a/builtin-rerere.c +++ b/builtin-rerere.c @@ -154,11 +154,15 @@ static int find_conflict(struct path_list *conflict) return error("Could not read index"); for (i = 0; i + 2 < active_nr; i++) { struct cache_entry *e1 = active_cache[i]; - struct cache_entry *e2 = active_cache[i + 1]; - struct cache_entry *e3 = active_cache[i + 2]; - if (ce_stage(e1) == 1 && ce_stage(e2) == 2 && - ce_stage(e3) == 3 && ce_same_name(e1, e2) && - ce_same_name(e1, e3)) { + struct cache_entry *e2 = active_cache[i+1]; + struct cache_entry *e3 = active_cache[i+2]; + if (ce_stage(e1) == 1 && + ce_stage(e2) == 2 && + ce_stage(e3) == 3 && + ce_same_name(e1, e2) && ce_same_name(e1, e3) && + S_ISREG(ntohl(e1->ce_mode)) && + S_ISREG(ntohl(e2->ce_mode)) && + S_ISREG(ntohl(e3->ce_mode))) { path_list_insert((const char *)e1->name, conflict); i += 2; } From ffa84ffb77b06f9793967fb4f4dd5c946da8e341 Mon Sep 17 00:00:00 2001 From: Roland Dreier Date: Sun, 25 Feb 2007 09:34:27 -0800 Subject: [PATCH 189/201] Allow arbitrary number of arguments to git-pack-objects If a repository ever gets in a situation where there are too many packs (more than 60 or so), perhaps because of frequent use of git-fetch -k or incremental git-repack, then it becomes impossible to fully repack the repository with git-repack -a. That command just dies with the cryptic message fatal: too many internal rev-list options This message comes from git-pack-objects, which is passed one command line option like --unpacked=pack-.pack for each pack file to be repacked. However, the current code has a static limit of 64 command line arguments and just aborts if more arguments are passed to it. Fix this by dynamically allocating the array of command line arguments, and doubling the size each time it overflows. Signed-off-by: Roland Dreier Signed-off-by: Junio C Hamano --- builtin-pack-objects.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 3824ee33ac..971388276a 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -1551,9 +1551,12 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) int use_internal_rev_list = 0; int thin = 0; int i; - const char *rp_av[64]; + const char **rp_av; + int rp_ac_alloc = 64; int rp_ac; + rp_av = xcalloc(rp_ac_alloc, sizeof(*rp_av)); + rp_av[0] = "pack-objects"; rp_av[1] = "--objects"; /* --thin will make it --objects-edge */ rp_ac = 2; @@ -1626,8 +1629,11 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) !strcmp("--reflog", arg) || !strcmp("--all", arg)) { use_internal_rev_list = 1; - if (ARRAY_SIZE(rp_av) - 1 <= rp_ac) - die("too many internal rev-list options"); + if (rp_ac >= rp_ac_alloc - 1) { + rp_ac_alloc = alloc_nr(rp_ac_alloc); + rp_av = xrealloc(rp_av, + rp_ac_alloc * sizeof(*rp_av)); + } rp_av[rp_ac++] = arg; continue; } From d2dc6222d4e2f7fa5efc82175d14d60d7b804687 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 25 Feb 2007 10:53:42 -0800 Subject: [PATCH 190/201] Add Release Notes to prepare for 1.5.0.2 Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.5.0.2.txt | 59 ++++++++++++++++++++++++++++++ RelNotes | 2 +- 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 Documentation/RelNotes-1.5.0.2.txt diff --git a/Documentation/RelNotes-1.5.0.2.txt b/Documentation/RelNotes-1.5.0.2.txt new file mode 100644 index 0000000000..4dc1344859 --- /dev/null +++ b/Documentation/RelNotes-1.5.0.2.txt @@ -0,0 +1,59 @@ +GIT v1.5.0.2 Release Notes +========================== + +Fixes since v1.5.0.1 +-------------------- + +* Bugfixes + + - 'git diff maint master next' did not correctly give combined + diff across three trees. + + - 'git fast-import' portability fix for Solaris. + + - 'git show-ref --verify' without arguments did not error out + but segfaulted. + + - 'git diff :tracked-file `pwd`/an-untracked-file' gave an extra + slashes after a/ and b/. + + - 'git format-patch' produced too long filenames if the commit + message had too long line at the beginning. + + - Running 'make all' and then without changing anything + running 'make install' still rebuilt some files. This + was inconvenient when building as yourself and then + installing as root (especially problematic when the source + directory is on NFS and root is mapped to nobody). + + - 'git-rerere' failed to deal with two unconflicted paths that + sorted next to each other. + + - 'git-rerere' attempted to open(2) a symlink and failed if + there was a conflict. Since a conflicting change to a + symlink would not benefit from rerere anyway, the command + now ignores conflicting changes to symlinks. + + - 'git-repack' did not like to pass more than 64 arguments + internally to underlying 'rev-list' logic, which made it + impossible to repack after accumulating many (small) packs + in the repository. + +* Documentation updates + + - added and clarified core.bare, core.legacyheaders configurations. + + - updated "git-clone --depth" documentation. + +* Assorted git-gui fixes. + + +-- +exec >/var/tmp/1 +O=v1.5.0.1-35-gffa84ff +echo O=`git describe maint` +git shortlog --no-merges $O..maint + +#Local Variables: +#mode: text +#End: diff --git a/RelNotes b/RelNotes index 63941cdfa9..5308f6b956 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes-1.5.0.1.txt \ No newline at end of file +Documentation/RelNotes-1.5.0.2.txt \ No newline at end of file From 17e48368751ae8df7ed0332ae8ef900ce983fce6 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 24 Feb 2007 18:18:22 -0800 Subject: [PATCH 191/201] Add test-chmtime: a utility to change mtime on files This is intended to be a portable replacement for our usage of date(1), touch(1), and Perl one-liners in tests. Usage: test-chtime (+|=|-|=+|=-) ..." '+' increments the mtime on the files by '-' decrements the mtime on the files by '=' sets the mtime on the file to exactly '=+' and '=-' sets the mtime on the file to after or before the current time. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano --- .gitignore | 1 + Makefile | 5 ++++- test-chmtime.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 test-chmtime.c diff --git a/.gitignore b/.gitignore index f15155d1b7..eb8a1f8606 100644 --- a/.gitignore +++ b/.gitignore @@ -139,6 +139,7 @@ git-whatchanged git-write-tree git-core-*/?* gitweb/gitweb.cgi +test-chmtime test-date test-delta test-dump-cache-tree diff --git a/Makefile b/Makefile index e51b448c78..8a42be9bab 100644 --- a/Makefile +++ b/Makefile @@ -829,7 +829,7 @@ GIT-CFLAGS: .FORCE-GIT-CFLAGS export NO_SVN_TESTS -test: all +test: all test-chmtime$X $(MAKE) -C t/ all test-date$X: test-date.c date.o ctype.o @@ -844,6 +844,9 @@ test-dump-cache-tree$X: dump-cache-tree.o $(GITLIBS) test-sha1$X: test-sha1.o $(GITLIBS) $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) +test-chmtime$X: test-chmtime.c + $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $< + check-sha1:: test-sha1$X ./test-sha1.sh diff --git a/test-chmtime.c b/test-chmtime.c new file mode 100644 index 0000000000..90da448ebe --- /dev/null +++ b/test-chmtime.c @@ -0,0 +1,61 @@ +#include "git-compat-util.h" +#include + +static const char usage_str[] = "(+|=|=+|=-|-) ..."; + +int main(int argc, const char *argv[]) +{ + int i; + int set_eq; + long int set_time; + char *test; + const char *timespec; + + if (argc < 3) + goto usage; + + timespec = argv[1]; + set_eq = (*timespec == '=') ? 1 : 0; + if (set_eq) { + timespec++; + if (*timespec == '+') { + set_eq = 2; /* relative "in the future" */ + timespec++; + } + } + set_time = strtol(timespec, &test, 10); + if (*test) { + fprintf(stderr, "Not a base-10 integer: %s\n", argv[1] + 1); + goto usage; + } + if ((set_eq && set_time < 0) || set_eq == 2) { + time_t now = time(NULL); + set_time += now; + } + + for (i = 2; i < argc; i++) { + struct stat sb; + struct utimbuf utb; + + if (stat(argv[i], &sb) < 0) { + fprintf(stderr, "Failed to stat %s: %s\n", + argv[i], strerror(errno)); + return -1; + } + + utb.actime = sb.st_atime; + utb.modtime = set_eq ? set_time : sb.st_mtime + set_time; + + if (utime(argv[i], &utb) < 0) { + fprintf(stderr, "Failed to modify time on %s: %s\n", + argv[i], strerror(errno)); + return -1; + } + } + + return 0; + +usage: + fprintf(stderr, "Usage: %s %s\n", argv[0], usage_str); + return -1; +} From 56cf9806a97f5fe9a91c38aa3eb8cec0c76480e6 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 24 Feb 2007 16:59:52 -0800 Subject: [PATCH 192/201] Update tests to use test-chmtime test-lib: Make sure test-chmtime has been built before starting. t4200-rerere: Removed non-portable date dependency and avoid touch Avoid "test -a" which isn't portable, either lib-git-svn: Use test-chmtime instead of Perl one-liner to poke Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano --- t/lib-git-svn.sh | 2 +- t/t4200-rerere.sh | 41 ++++++++++++++--------------------------- t/test-lib.sh | 6 ++++++ 3 files changed, 21 insertions(+), 28 deletions(-) diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index 27ad3b70f9..f6fe78cd27 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh @@ -46,5 +46,5 @@ rawsvnrepo="$svnrepo" svnrepo="file://$svnrepo" poke() { - perl -e '@x = stat($ARGV[0]); utime($x[8], $x[9] + 1, $ARGV[0])' "$1" + test-chmtime +1 "$1" } diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh index c571a1bd74..639d45fcec 100755 --- a/t/t4200-rerere.sh +++ b/t/t4200-rerere.sh @@ -112,39 +112,26 @@ rr2=.git/rr-cache/$sha2 mkdir $rr2 echo Hello > $rr2/preimage -case "$(date -d @11111111 +%s 2>/dev/null)" in -11111111) - # 'date' must be able to take arbitrary input with @11111111 notation. - # for this test to succeed. We should fix this part using more - # portable script someday. +almost_15_days_ago=$((60-15*86400)) +just_over_15_days_ago=$((-1-15*86400)) +almost_60_days_ago=$((60-60*86400)) +just_over_60_days_ago=$((-1-60*86400)) - now=$(date +%s) - almost_15_days_ago=$(($now+60-15*86400)) - just_over_15_days_ago=$(($now-1-15*86400)) - almost_60_days_ago=$(($now+60-60*86400)) - just_over_60_days_ago=$(($now-1-60*86400)) - predate1="$(date -d "@$almost_60_days_ago" +%Y%m%d%H%M.%S)" - predate2="$(date -d "@$almost_15_days_ago" +%Y%m%d%H%M.%S)" - postdate1="$(date -d "@$just_over_60_days_ago" +%Y%m%d%H%M.%S)" - postdate2="$(date -d "@$just_over_15_days_ago" +%Y%m%d%H%M.%S)" +test-chmtime =$almost_60_days_ago $rr/preimage +test-chmtime =$almost_15_days_ago $rr2/preimage - touch -m -t "$predate1" $rr/preimage - touch -m -t "$predate2" $rr2/preimage +test_expect_success 'garbage collection (part1)' 'git rerere gc' - test_expect_success 'garbage collection (part1)' 'git rerere gc' +test_expect_success 'young records still live' \ + "test -f $rr/preimage && test -f $rr2/preimage" - test_expect_success 'young records still live' \ - "test -f $rr/preimage -a -f $rr2/preimage" +test-chmtime =$just_over_60_days_ago $rr/preimage +test-chmtime =$just_over_15_days_ago $rr2/preimage - touch -m -t "$postdate1" $rr/preimage - touch -m -t "$postdate2" $rr2/preimage +test_expect_success 'garbage collection (part2)' 'git rerere gc' - test_expect_success 'garbage collection (part2)' 'git rerere gc' - - test_expect_success 'old records rest in peace' \ - "test ! -f $rr/preimage -a ! -f $rr2/preimage" - ;; -esac +test_expect_success 'old records rest in peace' \ + "test ! -f $rr/preimage && test ! -f $rr2/preimage" test_done diff --git a/t/test-lib.sh b/t/test-lib.sh index a403fe042b..c0754747fb 100755 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -264,6 +264,12 @@ test -d ../templates/blt || { error "You haven't built things yet, have you?" } +if ! test -x ../test-chmtime; then + echo >&2 'You need to build test-chmtime:' + echo >&2 'Run "make test-chmtime" in the source (toplevel) directory' + exit 1 +fi + # Test repository test=trash rm -fr "$test" From 6c09c451389ade6477d98b05d277aab0dd2f272e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 24 Feb 2007 22:26:33 -0800 Subject: [PATCH 193/201] diff --cached: give more sensible error message when HEAD is yet to be created. It is not like the user said 'diff --cached HEAD', so complaining about HEAD not being a valid commit, while technically might be correct, is not very helpful. Signed-off-by: Junio C Hamano --- builtin-diff.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/builtin-diff.c b/builtin-diff.c index c387ebb16c..67f49329bf 100644 --- a/builtin-diff.c +++ b/builtin-diff.c @@ -261,6 +261,8 @@ int cmd_diff(int argc, const char **argv, const char *prefix) break; else if (!strcmp(arg, "--cached")) { add_head(&rev); + if (!rev.pending.nr) + die("No HEAD commit to compare with (yet)"); break; } } From 308efc10d890265a4c62a8b58f025aaf8a1f244d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 25 Feb 2007 18:17:15 -0800 Subject: [PATCH 194/201] merge-index: fix longstanding bug in merging symlinks Ancient commit e2b6a9d0 added code to pass "file modes" from merge-index to merge-one-file, and then later commit 54dd99a1 wanted to make sure we do not end up creating a nonsense symlink that points at a path whose name contains conflict markers. However, nobody noticed that the code in merge-index added by e2b6a9d0 were stripping the S_IFMT bits and the code in 54dd99a1 was meaningless. This fixes it. Signed-off-by: Junio C Hamano --- merge-index.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merge-index.c b/merge-index.c index a9983dd78a..7027d78659 100644 --- a/merge-index.c +++ b/merge-index.c @@ -60,7 +60,7 @@ static int merge_entry(int pos, const char *path) break; found++; strcpy(hexbuf[stage], sha1_to_hex(ce->sha1)); - sprintf(ownbuf[stage], "%o", ntohl(ce->ce_mode) & (~S_IFMT)); + sprintf(ownbuf[stage], "%o", ntohl(ce->ce_mode)); arguments[stage] = hexbuf[stage]; arguments[stage + 4] = ownbuf[stage]; } while (++pos < active_nr); From 17cd29b25c0f480a3c6e1dd9617d9c6046e3c280 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 25 Feb 2007 18:42:07 -0800 Subject: [PATCH 195/201] merge-recursive: fix longstanding bug in merging symlinks Commit 3af244ca added unlink(2) before running symlink(2) to update the working tree with the merge result, but it was unlinking a wrong path. This resulted in loss of the path pointed by a symlink. Signed-off-by: Junio C Hamano --- merge-recursive.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merge-recursive.c b/merge-recursive.c index 58989424d7..397a7ad85b 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -589,7 +589,7 @@ static void update_file_flags(const unsigned char *sha, memcpy(lnk, buf, size); lnk[size] = '\0'; mkdir_p(path, 0777); - unlink(lnk); + unlink(path); symlink(lnk, path); } else die("do not know what to do with %06o %s '%s'", From 4fc970c43884e6215c13d6944a2def1eb2134ed5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 25 Feb 2007 22:24:47 -0800 Subject: [PATCH 196/201] diff --cc: fix display of symlink conflicts during a merge. "git-diff-files --cc" to show conflicts during merge did not pass the correct mode information for the working tree down, and showed bogus combined diff. Signed-off-by: Junio C Hamano --- combine-diff.c | 25 +++++++++++++++++++++---- diff-lib.c | 16 +++++++++++++--- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/combine-diff.c b/combine-diff.c index a5f2c8dd4a..6b7c6be959 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -678,9 +678,25 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, else { /* Used by diff-tree to read from the working tree */ struct stat st; - int fd; - if (0 <= (fd = open(elem->path, O_RDONLY)) && - !fstat(fd, &st)) { + int fd = -1; + + if (lstat(elem->path, &st) < 0) + goto deleted_file; + + if (S_ISLNK(st.st_mode)) { + int len = st.st_size; + result_size = len; + result = xmalloc(len + 1); + if (result_size != readlink(elem->path, result, len)) { + error("readlink(%s): %s", elem->path, + strerror(errno)); + return; + } + result[len] = 0; + elem->mode = canon_mode(st.st_mode); + } + else if (0 <= (fd = open(elem->path, O_RDONLY)) && + !fstat(fd, &st)) { int len = st.st_size; int sz = 0; @@ -698,11 +714,12 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, result[len] = 0; } else { - /* deleted file */ + deleted_file: result_size = 0; elem->mode = 0; result = xcalloc(1, 1); } + if (0 <= fd) close(fd); } diff --git a/diff-lib.c b/diff-lib.c index 556d5345bf..60c0fa6488 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -41,17 +41,27 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed) path_len = ce_namelen(ce); - dpath = xmalloc (combine_diff_path_size (5, path_len)); + dpath = xmalloc(combine_diff_path_size(5, path_len)); dpath->path = (char *) &(dpath->parent[5]); dpath->next = NULL; dpath->len = path_len; memcpy(dpath->path, ce->name, path_len); dpath->path[path_len] = '\0'; - dpath->mode = 0; hashclr(dpath->sha1); memset(&(dpath->parent[0]), 0, - sizeof(struct combine_diff_parent)*5); + sizeof(struct combine_diff_parent)*5); + + if (lstat(ce->name, &st) < 0) { + if (errno != ENOENT && errno != ENOTDIR) { + perror(ce->name); + continue; + } + if (silent_on_removed) + continue; + } + else + dpath->mode = canon_mode(st.st_mode); while (i < entries) { struct cache_entry *nce = active_cache[i]; From c5ddca1fff44896b7e94801da75392ccc234060c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 25 Feb 2007 23:26:11 -0800 Subject: [PATCH 197/201] Documentation: describe "-f/-t/-m" options to "git-remote add" Signed-off-by: Junio C Hamano --- Documentation/git-remote.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index a60c31a315..a2ff8f098e 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -31,6 +31,19 @@ subcommands are available to perform operations on the remotes. Adds a remote named for the repository at . The command `git fetch ` can then be used to create and update remote-tracking branches /. ++ +With `-f` option, `git fetch ` is run immediately after +the remote information is set up. ++ +With `-t ` option, instead of the default glob +refspec for the remote to track all branches under +`$GIT_DIR/remotes//`, a refspec to track only `` +is created. You can give more than one `-t ` to track +multiple branche without grabbing all branches. ++ +With `-m ` option, `$GIT_DIR/remotes//HEAD` is set +up to point at remote's `` branch instead of whatever +branch the `HEAD` at the remote repository actually points at. 'show':: From 4e5104c1fc42e440b9f086d428157d45897e1273 Mon Sep 17 00:00:00 2001 From: Pavel Roskin Date: Wed, 21 Feb 2007 00:03:36 -0500 Subject: [PATCH 198/201] git-remote: support remotes with a dot in the name [jc: the original from Pavel was limiting the variable names to only fetch and url, but I loosened it to take valid variable names.] [jc: cherry-picked from 'master', since people seem to be reinventing this many times.] Signed-off-by: Pavel Roskin Signed-off-by: Junio C Hamano --- git-remote.perl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-remote.perl b/git-remote.perl index c56c5a84a4..670bafb6d0 100755 --- a/git-remote.perl +++ b/git-remote.perl @@ -67,7 +67,7 @@ sub list_remote { $git->command(qw(config --get-regexp), '^remote\.'); }; for (@remotes) { - if (/^remote\.([^.]*)\.(\S*)\s+(.*)$/) { + if (/^remote\.(\S+?)\.([^.\s]+)\s+(.*)$/) { add_remote_config(\%seen, $1, $2, $3); } } From 0d9b9ab1284ce125fd49cf7dbf4d28e0540cf035 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 25 Feb 2007 23:58:50 -0800 Subject: [PATCH 199/201] GIT 1.5.0.2 Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.5.0.2.txt | 28 +++++++++++++++++----------- GIT-VERSION-GEN | 2 +- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/Documentation/RelNotes-1.5.0.2.txt b/Documentation/RelNotes-1.5.0.2.txt index 4dc1344859..b061e50ff0 100644 --- a/Documentation/RelNotes-1.5.0.2.txt +++ b/Documentation/RelNotes-1.5.0.2.txt @@ -6,6 +6,14 @@ Fixes since v1.5.0.1 * Bugfixes + - Automated merge conflict handling when changes to symbolic + links conflicted were completely broken. The merge-resolve + strategy created a regular file with conflict markers in it + in place of the symbolic link. The default strategy, + merge-recursive was even more broken. It removed the path + that was pointed at by the symbolic link. Both of these + problems have been fixed. + - 'git diff maint master next' did not correctly give combined diff across three trees. @@ -39,21 +47,19 @@ Fixes since v1.5.0.1 impossible to repack after accumulating many (small) packs in the repository. + - 'git-diff' to review the combined diff during a conflicted + merge were not reading the working tree version correctly + when changes to a symbolic link conflicted. It should have + read the data using readlink(2) but read from the regular + file the symbolic link pointed at. + + - 'git-remote' did not like period in a remote's name. + * Documentation updates - added and clarified core.bare, core.legacyheaders configurations. - updated "git-clone --depth" documentation. + * Assorted git-gui fixes. - - --- -exec >/var/tmp/1 -O=v1.5.0.1-35-gffa84ff -echo O=`git describe maint` -git shortlog --no-merges $O..maint - -#Local Variables: -#mode: text -#End: diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 9133a00b23..32a9422336 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.5.0.1.GIT +DEF_VER=v1.5.0.2.GIT LF=' ' From 047f636d903c773e237f684826cc68c79a1a0075 Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V" Date: Sat, 24 Feb 2007 21:02:56 +0530 Subject: [PATCH 200/201] Documentation: document remote..tagopt Update config.txt with info regarding tagopt option Signed-off-by: Aneesh Kumar K.V Signed-off-by: Junio C Hamano --- Documentation/config.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/config.txt b/Documentation/config.txt index 6309d89b4b..d2b4a05ca5 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -483,6 +483,10 @@ remote..uploadpack:: The default program to execute on the remote side when fetching. See option \--exec of gitlink:git-fetch-pack[1]. +remote..tagopt:: + Setting this value to --no-tags disables automatic tag following when fetching + from remote + remotes.:: The list of remotes which are fetched by "git remote update ". See gitlink:git-remote[1]. From c260d790c85c07a5f50235f664c36725deedfb10 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 26 Feb 2007 01:16:01 -0800 Subject: [PATCH 201/201] Documentation: link in 1.5.0.2 material to the top documentation page. Signed-off-by: Junio C Hamano --- Documentation/git.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/git.txt b/Documentation/git.txt index aa3acc0466..9a74747989 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -35,7 +35,9 @@ ifdef::stalenotes[] You are reading the documentation for the latest version of git. Documentation for older releases are available here: -* link:v1.5.0.1/git.html[documentation for release 1.5.0.1] +* link:v1.5.0.2/git.html[documentation for release 1.5.0.2] + +* link:v1.5.0.2/RelNotes-1.5.0.2.txt[release notes for 1.5.0.2] * link:v1.5.0.1/RelNotes-1.5.0.1.txt[release notes for 1.5.0.1]