From 81347223066fee64997cbb731925bc8c1abd35d7 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Mon, 12 Feb 2007 22:48:56 -0500 Subject: 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 diff --git a/git-gui.sh b/git-gui.sh index f5010dd..7ecb98b 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. -- cgit v0.10.2-6-g49f6 From 54acdd95b81d3675381a749e7cd7fc14956853c3 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 13 Feb 2007 23:15:25 -0500 Subject: 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 diff --git a/git-gui.sh b/git-gui.sh index 7ecb98b..444cc0a 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -297,6 +297,38 @@ proc ask_popup {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 if { [catch {set _gitdir $env(GIT_DIR)}] -- cgit v0.10.2-6-g49f6 From cdf6e08880b4924fa717bd6ca700081f445c9065 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 13 Feb 2007 23:43:48 -0500 Subject: 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 diff --git a/git-gui.sh b/git-gui.sh index 444cc0a..f8d4db2 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 -- cgit v0.10.2-6-g49f6 From 5ac58f5ba1392416a911c618abba6d874987a1b0 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 14 Feb 2007 00:10:20 -0500 Subject: 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 diff --git a/git-gui.sh b/git-gui.sh index f8d4db2..b3a80c6 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 { -- cgit v0.10.2-6-g49f6 From ed3adde081645c5f93685b22d39f2d74e068873d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 14 Feb 2007 00:28:00 -0500 Subject: 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 diff --git a/git-gui.sh b/git-gui.sh index b3a80c6..04afb36 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" -- cgit v0.10.2-6-g49f6 From 26370f73c0e82bd67103e0868f84a44b536ff3fc Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 14 Feb 2007 01:55:16 -0500 Subject: 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 diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 9966126..2741c1e 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 -- cgit v0.10.2-6-g49f6 From b2741f63d43a17ce9dafd1f97614bd6dbba72887 Mon Sep 17 00:00:00 2001 From: Andy Parkins Date: Tue, 13 Feb 2007 15:12:45 +0000 Subject: 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 diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 9371788..84520e7 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(); -- cgit v0.10.2-6-g49f6 From 6c510bee2013022fbce52f4b0ec0cc593fc0cc48 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 13 Feb 2007 11:07:23 -0800 Subject: 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 diff --git a/Makefile b/Makefile index 40bdcff..60496ff 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 c62b0b0..9c019e8 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 d821071..ffe0212 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 0000000..13beb70 --- /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 13b9b6c..561587c 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 c2641dd..472a9ef 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 54c22f8..2fa0960 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 8ad7fad..6ec67b2 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; -- cgit v0.10.2-6-g49f6 From d7f4633405acf3dc09798a759463c616c7c49dfd Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 13 Feb 2007 18:16:12 -0800 Subject: 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 diff --git a/config.c b/config.c index ffe0212..e8ae919 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 13beb70..898bfe3 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 2fa0960..570e32a 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; -- cgit v0.10.2-6-g49f6 From 634ede32ae7d4c76e96e88f9cd5c1b3a70ea08ac Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 14 Feb 2007 14:54:00 -0800 Subject: 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 diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh new file mode 100755 index 0000000..58a4d86 --- /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: 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 diff --git a/git-gui.sh b/git-gui.sh index 04afb36..9ce5a3b 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 \ -- cgit v0.10.2-6-g49f6 From 24424fc2f7cd7b3875fe6863587f47c043ac449d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 15 Feb 2007 01:46:27 -0800 Subject: 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 diff --git a/git-fetch.sh b/git-fetch.sh index ca984e7..d230995 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. -- cgit v0.10.2-6-g49f6 From b90d479255d3b47e3604493b58e271cb9cd8dccd Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Fri, 16 Feb 2007 00:24:03 -0500 Subject: 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 diff --git a/git-gui.sh b/git-gui.sh index 9ce5a3b..e789801 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" -- cgit v0.10.2-6-g49f6 From dc7b24364dcbe9ab74d937c92af9999ac1a2db0b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 17 Feb 2007 13:12:52 -0800 Subject: 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 diff --git a/builtin-apply.c b/builtin-apply.c index 3fefdac..fc1d673 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 0000000..0e8ea7e --- /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 -- cgit v0.10.2-6-g49f6 From 6716027108f426c83038b05baf3f20ceefe6fbd1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 17 Feb 2007 12:37:25 -0800 Subject: 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 diff --git a/builtin-apply.c b/builtin-apply.c index 3fefdac..45c4acb 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 58a4d86..723b29a 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: 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 diff --git a/git-gui.sh b/git-gui.sh index e789801..551c11c 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -361,6 +361,24 @@ set _reponame [lindex [file split \ ###################################################################### ## +## 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 set rescan_active 0 @@ -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 -- cgit v0.10.2-6-g49f6 From 700ea47936e393a217f4174f80a19aa01f698b56 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 17 Feb 2007 18:12:46 -0800 Subject: Teach 'git apply' to look at $HOME/.gitconfig even outside of a repository Signed-off-by: Junio C Hamano diff --git a/builtin-apply.c b/builtin-apply.c index fc1d673..2784db2 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]; -- cgit v0.10.2-6-g49f6 From c5a8c3ecd7fcec68b05b38640d4eac4e1cc7f50f Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 18 Feb 2007 17:27:24 +0100 Subject: diff --check: use colour Reuse the colour handling of the regular diff. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index 13b9b6c..d5caf6d 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) -- cgit v0.10.2-6-g49f6 From e63ccb84e3e29d428548669a7a1206d386a57d6d Mon Sep 17 00:00:00 2001 From: Fredrik Kuivinen Date: Sun, 18 Feb 2007 09:44:42 +0100 Subject: 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 diff --git a/configure.ac b/configure.ac index 7cfb3a0..3a8e778 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" -- cgit v0.10.2-6-g49f6 From f496454e0fd22d003e9c9b5c3742b12d526bc0a4 Mon Sep 17 00:00:00 2001 From: Christian Schlotter Date: Mon, 19 Feb 2007 13:35:35 +0100 Subject: 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 diff --git a/Documentation/RelNotes-1.5.0.txt b/Documentation/RelNotes-1.5.0.txt index 599efb8..daf4bdb 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 707376f..6d32c49 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 -- cgit v0.10.2-6-g49f6 From bc6b4f52fc79712db637ff90a472b82b32695e11 Mon Sep 17 00:00:00 2001 From: Jason Riedy Date: Mon, 19 Feb 2007 16:22:56 -0800 Subject: 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 diff --git a/Makefile b/Makefile index dae2919..f85fb7c 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 0000000..5541353 --- /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 105ac28..9863cf6 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) -- cgit v0.10.2-6-g49f6 From e326bce65c2a8b10f40c1869e13c7450a243b109 Mon Sep 17 00:00:00 2001 From: Jason Riedy Date: Mon, 19 Feb 2007 16:27:09 -0800 Subject: 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 diff --git a/fast-import.c b/fast-import.c index fd3b117..8720090 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"); -- cgit v0.10.2-6-g49f6 From 1e592d65b50dbec87fde9f4ef1b7fd8d90bf7b8c Mon Sep 17 00:00:00 2001 From: Theodore Ts'o Date: Sun, 18 Feb 2007 23:00:00 -0500 Subject: 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 diff --git a/Documentation/config.txt b/Documentation/config.txt index 3865535..d8e696f 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 a60c31a..06ba2e6 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 c56c5a8..6e473ec 100755 --- a/git-remote.perl +++ b/git-remote.perl @@ -303,6 +303,18 @@ elsif ($ARGV[0] eq 'show') { 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 @@ else { 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); } -- cgit v0.10.2-6-g49f6 From aea1945744214bf84908586af8be1c098a6f346d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 19 Feb 2007 17:58:58 -0800 Subject: 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 diff --git a/builtin-apply.c b/builtin-apply.c index 2784db2..3f829fb 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); } -- cgit v0.10.2-6-g49f6 From 56185f49d03cae28048146e902089ea366c6cd6c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 19 Feb 2007 17:57:29 -0800 Subject: 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 diff --git a/builtin-apply.c b/builtin-apply.c index 3f829fb..053511e 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 0e8ea7e..816b5b8 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?" -- cgit v0.10.2-6-g49f6 From eac70c4f64a618744e05d4a5be61a356c0011033 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 20 Feb 2007 03:45:49 +0100 Subject: apply: fix memory leak in prefix_one() Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/builtin-apply.c b/builtin-apply.c index 053511e..2a40af3 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); } } -- cgit v0.10.2-6-g49f6 From 59d3f541cfe48345d708330bc19d8a880c80c595 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 20 Feb 2007 01:08:48 +0100 Subject: 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 diff --git a/builtin-name-rev.c b/builtin-name-rev.c index b4f15cc..1b06b4a 100644 --- a/builtin-name-rev.c +++ b/builtin-name-rev.c @@ -57,13 +57,17 @@ copy_data: 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; - - snprintf(buffer, sizeof(buffer), "%s~%d", n->tip_name, n->generation); - - return buffer; + 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); + + return buffer; + } } int cmd_name_rev(int argc, const char **argv, const char *prefix) -- cgit v0.10.2-6-g49f6 From c4025103faf02a1b457b945cf5e4e12c97fa72d7 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 19 Feb 2007 03:14:59 +0100 Subject: 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 diff --git a/revision.c b/revision.c index 15bdaf6..4f298ba 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--; } -- cgit v0.10.2-6-g49f6 From 32043c9f8c60fc03b0b6a324c559d98094729323 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 14 Feb 2007 12:48:14 +0100 Subject: 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 diff --git a/Documentation/config.txt b/Documentation/config.txt index 3865535..1dd90d8 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 40bdcff..203aac4 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 0f9051d..f1433a4 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 d821071..b0c0948 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) -- cgit v0.10.2-6-g49f6 From a53b12c3a22b62c3b47c527f6641bb5ceceb0c1b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 20 Feb 2007 00:44:35 -0800 Subject: Link 1.5.0.1 documentation from the main page. Signed-off-by: Junio C Hamano diff --git a/Documentation/git.txt b/Documentation/git.txt index 3d8be59..aa3acc0 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] -- cgit v0.10.2-6-g49f6 From 3efb1f343a9d6214a59a814029cab96638e9b915 Mon Sep 17 00:00:00 2001 From: Jason Riedy Date: Tue, 20 Feb 2007 17:34:56 -0800 Subject: 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 diff --git a/fast-import.c b/fast-import.c index 8720090..1ae125a 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"); -- cgit v0.10.2-6-g49f6 From ee40599330e75d9266672ed5031b3f75764fea3d Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 18 Feb 2007 19:06:09 -0500 Subject: 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 diff --git a/git-gui.sh b/git-gui.sh index 551c11c..63848dc 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 {}} { -- cgit v0.10.2-6-g49f6 From 871f4c97ad7e021d1a0a98c80c5da77fcf70e4af Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 18 Feb 2007 21:06:48 -0500 Subject: 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 diff --git a/.gitignore b/.gitignore index c714d38..805ca2e 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 0000000..da2c076 --- /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 fd82d9d..66538ba 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 63848dc..cd3afed 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 -- cgit v0.10.2-6-g49f6 From 019f42a4ffe66e35cfe32a18c3def0183e6a0129 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 18 Feb 2007 21:08:04 -0500 Subject: 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 diff --git a/git-gui.sh b/git-gui.sh index cd3afed..4058130 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 \ -- cgit v0.10.2-6-g49f6 From cff0302c14522ba7b34265ee39f23e6321a4777d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 20 Feb 2007 01:51:22 -0800 Subject: Add prefixcmp() We have too many strncmp(a, b, strlen(b)). Signed-off-by: Junio C Hamano diff --git a/git-compat-util.h b/git-compat-util.h index 9863cf6..d027c36 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 -- cgit v0.10.2-6-g49f6 From cc44c7655fe2dd0cfb46e841156634fe622df397 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 20 Feb 2007 01:53:29 -0800 Subject: 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 diff --git a/builtin-apply.c b/builtin-apply.c index abe3538..d678178 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 f613ac2..0c56de0 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 5669a16..db311bf 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 d0e7209..d0179b0 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 b097c88..afe4b0e 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 bcc6456..165917e 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 87d3d63..1489883 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 16c785f..ac0b9f6 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 6da3814..6abf498 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 2bfbdb7..cec2204 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 12e43d0..4df9fd0 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 af2de54..ad1e8c0 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 ac89eb2..4e1d5af 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 583da38b6..6ee6b0b 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 36f1ba6..2c3d14c 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 3824ee3..71113d8 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 3de9b3e..d080e30 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 c45649e..2b98ba3 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 8ba436d..e477155 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 3415551..cefb40d 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 318d959..978105b 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 d53deaa..a1c3411 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 edb4042..2f71a2a 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 0d94e40..bf6aee4 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 853f13f..ae0eddd 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 8055dda..28f8c1c 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 d351e02..8f8e898 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 50670dc..90fc1cf 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 7844888..8a8a13b 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 66f8d6f..cdbc23f 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 07589c3..fad13ab 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 3996bce..9b74ed2 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 32cd1f8..8e192c2 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 c787106..1fd2c3a 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 4dd1967..1fad852 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 b667463..0893fea 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 9f790a0..d9a4561 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 b128c01..eb77c9a 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 72e0962..fa9a0e7 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 ef3c76c..7b66228 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 7311c82..7f1dcc0 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 6387703..d347876 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 5b1794b..abab3b9 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 33e69db..512b660 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 e9d3f5a..dda67d2 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 8c08cf0..c983fc7 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 3648aae..d7876ca 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 2879c3d..d17a6ba 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]); } -- cgit v0.10.2-6-g49f6 From 599065a3bb94ae9f48e3808b8fafc8443017af28 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 20 Feb 2007 01:54:00 -0800 Subject: 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 diff --git a/builtin-archive.c b/builtin-archive.c index 0c56de0..8ea6cb1 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 db311bf..530b97f 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 cec2204..f35f2d0 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 71113d8..b5ed9ce 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 2b98ba3..979efcc 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 978105b..dd1d4c1 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 bf6aee4..402a8f7 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 28f8c1c..b04719e 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 cdbc23f..e74ecac 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 8e192c2..5d040fd 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 1fd2c3a..41bdd27 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 7b66228..96bfac4 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 d7876ca..804bbb6 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); -- cgit v0.10.2-6-g49f6 From 1968d77dd632a9a9e5c6f5681649e5e65ed13088 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 20 Feb 2007 01:55:07 -0800 Subject: 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 diff --git a/builtin-ls-tree.c b/builtin-ls-tree.c index 201defd..6472610 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 a1c3411..37addb2 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 402a8f7..67ae6ba 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 fad13ab..c3afee2 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 d9a4561..e6cd11d 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 eb77c9a..9ad6fd0 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 3eaf025..84df2fa 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 abab3b9..622afe3 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 d17a6ba..035e546 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]); } -- cgit v0.10.2-6-g49f6 From c0f7a6c33da7ec875fb084c9d97e471cd3a14535 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 21 Feb 2007 01:24:57 -0500 Subject: 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 diff --git a/git-gui.sh b/git-gui.sh index 4058130..039e7bb 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 } } -- cgit v0.10.2-6-g49f6 From 7391b2e9991e138786a2ac049ce7f276f7abce40 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 21 Feb 2007 01:29:05 -0500 Subject: 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 diff --git a/TODO b/TODO deleted file mode 100644 index b95a137..0000000 --- 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. -- cgit v0.10.2-6-g49f6 From 981193786fc30b9ee73b9f223a75642b4ed455b9 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 21 Feb 2007 01:33:59 -0500 Subject: 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 diff --git a/git-gui.sh b/git-gui.sh index 039e7bb..f84ba33 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 -- cgit v0.10.2-6-g49f6 From 34c6a82b8afca18eab28eeca659e29db0faabc62 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 19 Feb 2007 15:56:04 +0100 Subject: git grep: use pager Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano diff --git a/git.c b/git.c index 1fad852..83f3d90 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 }, -- cgit v0.10.2-6-g49f6 From b97e911643341cb31e6b97029b9ffd96fc675b1d Mon Sep 17 00:00:00 2001 From: Martin Waitz Date: Sat, 17 Feb 2007 10:13:10 +0100 Subject: 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 diff --git a/git-compat-util.h b/git-compat-util.h index d027c36..5d154fa 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 -- cgit v0.10.2-6-g49f6 From d2cd696322011eb13125930e747f0d2a7778b992 Mon Sep 17 00:00:00 2001 From: Alex Riesen Date: Tue, 20 Feb 2007 10:04:32 +0100 Subject: 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 diff --git a/t/t4016-diff-quote.sh b/t/t4016-diff-quote.sh index edde8f5..2e7cd5f 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" && -- cgit v0.10.2-6-g49f6 From 4a6b9bb60ab3cdb3a749ec43763845a7a60d40d4 Mon Sep 17 00:00:00 2001 From: Simon 'corecode' Schubert Date: Sun, 18 Feb 2007 18:17:08 +0100 Subject: 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 diff --git a/Documentation/git-cvsexportcommit.txt b/Documentation/git-cvsexportcommit.txt index 27d531b..555b823 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 870554e..d08216c 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -15,14 +15,21 @@ unless ($ENV{GIT_DIR} && -r $ENV{GIT_DIR}){ 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 @@ foreach my $f (@afiles) { 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 (@afiles) { 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 "Applying\n"; 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 $d (@dirs) { 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 (@afiles) { } 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 @@ foreach my $f (@dfiles) { 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 ($dirtypatch) { 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"; } -- cgit v0.10.2-6-g49f6 From 7b9a13ece8a1e7c0fd2a82d4bd4fcf0a9ce0e8c4 Mon Sep 17 00:00:00 2001 From: Theodore Ts'o Date: Tue, 20 Feb 2007 15:13:42 -0500 Subject: Add config_boolean() method to the Git perl module Signed-off-by: "Theodore Ts'o" Signed-off-by: Junio C Hamano diff --git a/perl/Git.pm b/perl/Git.pm index f2c156c..b5b1cf5 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 ) -- cgit v0.10.2-6-g49f6 From 1918278ea1019553b01297aad2caeed88e9092a4 Mon Sep 17 00:00:00 2001 From: Theodore Ts'o Date: Tue, 20 Feb 2007 15:13:43 -0500 Subject: 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 diff --git a/Documentation/config.txt b/Documentation/config.txt index d8e696f..f5c846f 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 06ba2e6..250761f 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 6e473ec..61244e9 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 @@ elsif ($ARGV[0] eq 'show') { } } 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 @@ else { 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); } -- cgit v0.10.2-6-g49f6 From 13e36ec51bee59c6433322b23323b75e9a635d35 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 20 Feb 2007 15:08:46 +0100 Subject: 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 --git a/diff.c b/diff.c index c3afee2..019ecbc 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; } } -- cgit v0.10.2-6-g49f6 From b5a40a57240cc292c32e691d3b3c550f6cf8d98c Mon Sep 17 00:00:00 2001 From: Pavel Roskin Date: Wed, 21 Feb 2007 00:03:36 -0500 Subject: 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 diff --git a/git-remote.perl b/git-remote.perl index 61244e9..bd70bf1 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); } } -- cgit v0.10.2-6-g49f6 From c24e9757e9f608ad3985ee9093d28ca5cd37f052 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 21 Feb 2007 01:14:22 -0800 Subject: t4119: add test for traditional patch and different p_value Signed-off-by: Junio C Hamano diff --git a/t/t4119-apply-config.sh b/t/t4119-apply-config.sh index 816b5b8..f9b9425 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 + 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 ' -- cgit v0.10.2-6-g49f6 From 6c912f5b04e3216a5487e03235a8454b754a464e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 21 Feb 2007 00:58:18 -0800 Subject: 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 diff --git a/builtin-apply.c b/builtin-apply.c index 2a40af3..1beebe5 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); + } } } -- cgit v0.10.2-6-g49f6 From c750da256a54f189de28d3deb054328d67f9b9be Mon Sep 17 00:00:00 2001 From: Michael Loeffler Date: Wed, 14 Feb 2007 17:03:12 +0100 Subject: 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 diff --git a/contrib/fast-import/import-tars.perl b/contrib/fast-import/import-tars.perl index 990c9e7..5585a8b 100755 --- a/contrib/fast-import/import-tars.perl +++ b/contrib/fast-import/import-tars.perl @@ -25,11 +25,14 @@ foreach my $tar_file (@ARGV) 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 { -- cgit v0.10.2-6-g49f6 From 9987d7c58a847ab1605ae3216ff1ca95b19f0ad1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 21 Feb 2007 14:31:10 -0800 Subject: 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 diff --git a/builtin-apply.c b/builtin-apply.c index 1beebe5..12f00e3 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 f9b9425..32e0d71 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?" -- cgit v0.10.2-6-g49f6 From 3e8a5db966c26a0a986161103d59683b909a6c78 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 21 Feb 2007 16:05:56 -0800 Subject: 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 diff --git a/builtin-apply.c b/builtin-apply.c index 12f00e3..c7d4bdd 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 32e0d71..55f4673 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?" -- cgit v0.10.2-6-g49f6 From fe6e0eecb03379e6acb742f77b0b5f589a7b7422 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 21 Feb 2007 16:18:45 -0800 Subject: t4119: test autocomputing -p for traditional diff input. Signed-off-by: Junio C Hamano diff --git a/t/t4119-apply-config.sh b/t/t4119-apply-config.sh index 55f4673..620a920 100755 --- a/t/t4119-apply-config.sh +++ b/t/t4119-apply-config.sh @@ -33,24 +33,28 @@ sed -e ' /^+++ /s|file1|b/sub/&| ' gpatch.file >gpatch-ab-sub.file && -test_expect_success 'apply --whitespace=strip' ' - - rm -f sub/file1 && - cp saved sub/file1 && - git update-index --refresh && - - git apply --whitespace=strip patch.file && - if grep " " sub/file1 +check_result () { + if grep " " "$1" then echo "Eh?" false - elif grep B sub/file1 + elif grep B "$1" then echo Happy else echo "Huh?" false fi +} + +test_expect_success 'apply --whitespace=strip' ' + + rm -f sub/file1 && + cp saved sub/file1 && + git update-index --refresh && + + git apply --whitespace=strip patch.file && + 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 -- cgit v0.10.2-6-g49f6 From e4a15f40bcd9e5679345d3cc74db3162310a7c83 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 21 Feb 2007 17:59:08 -0500 Subject: Document the new core.bare configuration option. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano diff --git a/Documentation/config.txt b/Documentation/config.txt index 3865535..4a22a00 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 -- cgit v0.10.2-6-g49f6 From 4917d2a66e8fa8c40ea6082c0fd8b58492c9444e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 21 Feb 2007 23:49:51 -0500 Subject: 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 diff --git a/Makefile b/Makefile index f85fb7c..64d29f7 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 -- cgit v0.10.2-6-g49f6 From aeabfa072564d9261fa80b1314949bdfd9b78632 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 22 Feb 2007 20:11:21 +0100 Subject: 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 diff --git a/builtin-apply.c b/builtin-apply.c index 3fefdac..17f4ad5 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; } -- cgit v0.10.2-6-g49f6 From 755b99d81539461645088085ea033a3b36152da5 Mon Sep 17 00:00:00 2001 From: Fredrik Kuivinen Date: Thu, 22 Feb 2007 21:28:12 +0100 Subject: 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 diff --git a/git-commit.sh b/git-commit.sh index ec506d9..476f4f1 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 && -- cgit v0.10.2-6-g49f6 From 75b62b489af7b62a5518c3f199d2a2776205e088 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 23 Feb 2007 05:20:32 +0100 Subject: 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 diff --git a/builtin-diff.c b/builtin-diff.c index a659020..c387ebb 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; -- cgit v0.10.2-6-g49f6 From 8565d2d853d85f246faa9bcde91aba3415a24d54 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 15 Feb 2007 11:43:56 +0100 Subject: 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 diff --git a/t/test-lib.sh b/t/test-lib.sh index 37822fc..a403fe0 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 -- cgit v0.10.2-6-g49f6 From d976acfd89a7ea539cdaf3fc806308d426b8007a Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 4 Jan 2007 00:45:03 -0800 Subject: git-svn: move authentication prompts into their own namespace I'm going to be reorganizing some more code. Signed-off-by: Eric Wong diff --git a/git-svn.perl b/git-svn.perl index d792a62..cbe4ed2 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -69,7 +69,7 @@ my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit, $_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 @@ my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext, '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 @@ my %cmd = ( '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 @@ 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, -- cgit v0.10.2-6-g49f6 From 4a87db0e12df48408501610605bd7cde81c6d20e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 4 Jan 2007 01:38:18 -0800 Subject: git-svn: cleanup: move process_rm around (it's only used in one function now) Signed-off-by: Eric Wong diff --git a/git-svn.perl b/git-svn.perl index cbe4ed2..fcef05c 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -40,7 +40,6 @@ if ($SVN::Core::VERSION lt '1.1.0') { } 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; } -- cgit v0.10.2-6-g49f6 From d81bf827192f0af6b1cca64d2cdbaac9b5ca2020 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 10 Jan 2007 01:22:38 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index fcef05c..5a3a877 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 @@ require SVN::Delta; 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 $_esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/; 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 @@ my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext, '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 @@ my %cmd = ( { %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: -- cgit v0.10.2-6-g49f6 From 336f1714ae4211f97a33e2f672ec9c3479b4e5ba Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 11 Jan 2007 02:14:43 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 5a3a877..55d9412 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -49,19 +49,28 @@ 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 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 @@ my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit, $_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 @@ my %cmd = ( '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 @@ use strict; 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 @@ use strict; 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; -- cgit v0.10.2-6-g49f6 From 9b981fc6596e369e82bc744e12afd5e54f2514f0 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 11 Jan 2007 12:14:21 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 55d9412..8abff90 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; -- cgit v0.10.2-6-g49f6 From d2866f9e1fbe24d9ac9e24501a9be07c3c189d34 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 11 Jan 2007 12:26:16 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 8abff90..5ff0c73 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -111,7 +111,7 @@ my %cmt_opts = ( 'edit|e' => \$_edit, 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}; -- cgit v0.10.2-6-g49f6 From 8164b6525ef6d95d4b656a8f4226f1758a611989 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 11 Jan 2007 15:35:55 -0800 Subject: git-svn: convert multi-init over to using Git::SVN Signed-off-by: Eric Wong diff --git a/git-svn.perl b/git-svn.perl index 5ff0c73..72f73ea 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -133,7 +133,7 @@ my %cmd = ( '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); } -- cgit v0.10.2-6-g49f6 From e7db67e6f18495332c4d688d3291b05851526a6e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 11 Jan 2007 17:09:26 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 72f73ea..02786f1 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 183ae3b..8d946d2 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` -- cgit v0.10.2-6-g49f6 From ad2f90851e03294b5b30f735252001bfc1bd9de3 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 11 Jan 2007 17:55:50 -0800 Subject: git-svn: add a test for show-ignore Signed-off-by: Eric Wong diff --git a/t/t9101-git-svn-props.sh b/t/t9101-git-svn-props.sh index e8133d8..622ea1c 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 -- cgit v0.10.2-6-g49f6 From 5969cbe13c7e65db6441632d58e7dee40795a980 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 11 Jan 2007 17:58:39 -0800 Subject: git-svn: convert show-ignore over to Git::SVN Signed-off-by: Eric Wong diff --git a/git-svn.perl b/git-svn.perl index 02786f1..e0bccbc 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -121,7 +121,7 @@ my %cmd = ( %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 { -- cgit v0.10.2-6-g49f6 From f8c9d1d27f250a7fe13d4d33a1a94604ad355529 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 12 Jan 2007 02:35:20 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index e0bccbc..3a4e413 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -6,7 +6,8 @@ use strict; 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 @@ $GIT_DIR = abs_path($ENV{GIT_DIR} || '.git'); $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 IO::File qw//; 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 @@ my %fc_opts = ( 'branch|b=s' => \@_branch_from, '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 @@ my %cmd = ( '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: -- cgit v0.10.2-6-g49f6 From e7f023c81a03ccdd25ce4b4c7ed77f367c8f7edd Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 12 Jan 2007 02:49:01 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 3a4e413..3e4f5b7 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -7,7 +7,8 @@ use vars qw/ $AUTHOR $VERSION $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 @@ 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 ($_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 @@ my %cmd = ( { '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. -- cgit v0.10.2-6-g49f6 From c843c464b83237fba65dc46a10133fda9f475cc5 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 12 Jan 2007 03:07:31 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 3e4f5b7..dd639a1 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; } -- cgit v0.10.2-6-g49f6 From 44320b9e0e279cd1f549e259d00753a02c86c21b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 13 Jan 2007 22:35:53 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index dd639a1..575d793 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -8,7 +8,8 @@ use vars qw/ $AUTHOR $VERSION $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 @@ 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,$_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 @@ my %cmd = ( '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_message { - my ($commit, $commit_msg) = (@_); - my %log_msg = ( msg => '' ); - open my $msg, '>', $commit_msg or croak $!; +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 $!; - 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 @@ __END__ 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 -- cgit v0.10.2-6-g49f6 From 396988e0b9cd00d5d13edb157b91dbd5050ef99f Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 13 Jan 2007 23:00:47 -0800 Subject: git-svn: get rid of Memoize for now... I may refactor more of this stuff into separate modules diff --git a/git-svn.perl b/git-svn.perl index 575d793..acc93b9 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -50,11 +50,7 @@ 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 IPC::Open3; -use Memoize; use Git; -memoize('revisions_eq'); -memoize('cmt_metadata'); -memoize('get_commit_time'); BEGIN { my $s; -- cgit v0.10.2-6-g49f6 From 1c8443b05074cfa466845c4dad98fe962f6dd4c2 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 14 Jan 2007 02:17:00 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index acc93b9..bc3504f 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -9,7 +9,7 @@ use vars qw/ $AUTHOR $VERSION $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 @@ 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, + $_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 @@ my %cmd = ( '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) { -- cgit v0.10.2-6-g49f6 From d7ad3bed8cfbaf21aeaaff2cd10e3696d8785b78 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 14 Jan 2007 03:14:28 -0800 Subject: git-svn: switch dcommit to using Git::SVN code Signed-off-by: Eric Wong diff --git a/git-svn.perl b/git-svn.perl index bc3504f..bf53b2d 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -107,7 +107,8 @@ my %cmd = ( 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 { -- cgit v0.10.2-6-g49f6 From 1ce255dc168cc1fcf849a7c82bdf45753b0dfe09 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 14 Jan 2007 23:21:16 -0800 Subject: git-svn: convert 'set-tree' command to use Git::SVN Signed-off-by: Eric Wong diff --git a/git-svn.perl b/git-svn.perl index bf53b2d..261e33d 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -24,16 +24,6 @@ $ENV{TZ} = 'UTC'; $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 @@ my %cmd = ( '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,81 +311,20 @@ 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); - 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); - } + 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"; } - $ENV{LC_ALL} = 'C'; - unlink $commit_msg; + $gs->set_tree($_) foreach @revs; + print "Done committing ",scalar @revs," revisions to SVN\n"; } sub cmd_dcommit { @@ -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; -- cgit v0.10.2-6-g49f6 From d05d72e07e49869fe988d4d99e6ac60711570db5 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 15 Jan 2007 22:59:26 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 261e33d..e75021b 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -60,16 +60,13 @@ $sha1_short = qr/[a-f\d]{4,40}/; 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 @@ my %cmd = ( 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 @@ my $rv = GetOptions(%opts, 'help|H|h' => \$_help, '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 8d946d2..0000000 --- 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 -- cgit v0.10.2-6-g49f6 From 706587fc6d56db1ba6c7207d4c0c456bac6f77c2 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 18 Jan 2007 17:50:01 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index e75021b..9d50d30 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -13,9 +13,8 @@ use vars qw/ $AUTHOR $VERSION $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 @@ my ($_stdin, $_help, $_edit, $_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 @@ my %cmd = ( '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 @@ my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd); 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 @@ out: 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 0000000..53318f1 --- /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 + -- cgit v0.10.2-6-g49f6 From 780a2f58e727386ae81223d7a5c16fbc55cfd9fa Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 18 Jan 2007 18:15:23 -0800 Subject: git-svn: fix a regression in dcommit that caused empty log messages Signed-off-by: Eric Wong diff --git a/git-svn.perl b/git-svn.perl index 9d50d30..b5a4cb0 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); -- cgit v0.10.2-6-g49f6 From f6f0987646069bff3a7674aca257fd84f939d960 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 18 Jan 2007 18:22:18 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index b5a4cb0..4084e06 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2015,6 +2015,7 @@ use vars qw/@ISA $config_dir/; 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 { -- cgit v0.10.2-6-g49f6 From 47e39c55c91993b94824b7a317ebeb965aaeb45a Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 21 Jan 2007 04:27:09 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 4084e06..15d65e2 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -119,8 +119,6 @@ my %cmd = ( \%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 @@ 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_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 @@ use strict; 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 -- cgit v0.10.2-6-g49f6 From 15710b6f34da26d30079dbc83c797a8335040b75 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 22 Jan 2007 02:20:33 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 15d65e2..8d49959 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 405b555..91fdfe9 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 &' -- cgit v0.10.2-6-g49f6 From 8b8fc06824cde2b314807e5e3a20e0adfd948cda Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 22 Jan 2007 11:44:57 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 8d49959..84f4679 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -14,7 +14,8 @@ $AUTHOR = 'Eric Wong '; $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 @@ 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); + '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 91fdfe9..3afec97 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 -- cgit v0.10.2-6-g49f6 From b805b44a923e32251af1abd4e8d7bf5f7d4d8ef6 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 22 Jan 2007 13:52:04 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 84f4679..f01fb9a 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -169,7 +169,11 @@ load_authors() if $_authors; 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 040da92..af61748 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 -- cgit v0.10.2-6-g49f6 From a2003abc23a5961534e8a0cc70b881eb78d54328 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 22 Jan 2007 15:22:50 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index f01fb9a..88c0227 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 3afec97..402b614 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 -- cgit v0.10.2-6-g49f6 From 07a1c95045a8c4983d3868f6070d9fa9ba5ff596 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 22 Jan 2007 15:47:41 -0800 Subject: 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 diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 6ce6a39..6daba24 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 88c0227..2e3d355 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}); -- cgit v0.10.2-6-g49f6 From 536c4b09370f3f443fbe87284d2378fd21f94350 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 23 Jan 2007 11:35:53 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 2e3d355..a70e7b9 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*$!) { -- cgit v0.10.2-6-g49f6 From 9bf046372b370fba8958ba6ef9dc63b232d7637c Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 23 Jan 2007 13:03:29 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index a70e7b9..de026b4 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; } -- cgit v0.10.2-6-g49f6 From e6434f876097f196acbd9a806637d0f6076752fd Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 23 Jan 2007 16:29:23 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index de026b4..d290a0d 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -748,35 +748,78 @@ sub sanitize_remote_name { $name; } -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 +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: - my $orig_url = eval { + $orig_url = eval { command_oneline('config', '--get', - "svn-remote.$repo_id.url") + "svn-remote.$self->{repo_id}.url") }; if ($orig_url && ($orig_url ne $url)) { - die "svn-remote.$repo_id.url already set: ", + 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"; - } - if (!$orig_url) { - command_noisy('config', - "svn-remote.$repo_id.url", $url); - } - command_noisy('config', '--add', - "svn-remote.$repo_id.fetch", - "$path:".$self->refname); } + 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) { + $self->init_remote_config($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 af61748..97798c4 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 -- cgit v0.10.2-6-g49f6 From 7f578c55af80e9346135004bd47099cbb451f859 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 24 Jan 2007 02:16:25 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index d290a0d..123d4d6 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 402b614..22b45a6 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 -- cgit v0.10.2-6-g49f6 From ef3cfaad19f2587ea4ff7d46574d9118f8d9555e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 24 Jan 2007 03:30:57 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 123d4d6..a19afb8 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: -- cgit v0.10.2-6-g49f6 From 1492b4245ad735e74f226bd796dc85de4f843739 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 25 Jan 2007 10:52:36 -0800 Subject: git-svn: add an odd test case that seems to cause segfaults over HTTP Signed-off-by: Eric Wong diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 22b45a6..615c863 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 -- cgit v0.10.2-6-g49f6 From 97f6987afaae239f7e3ae3944e0b29343b43a894 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 25 Jan 2007 11:53:13 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index a19afb8..6ff3a8c 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 97798c4..5355243 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 74a45ec..f6d84ba 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 && -- cgit v0.10.2-6-g49f6 From e5a0b240fc237af6165b728ae9c79288ef624d3b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 25 Jan 2007 15:44:54 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 6ff3a8c..0e2348a 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 615c863..a6ba0fa 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 -- cgit v0.10.2-6-g49f6 From 3ebe8df7f690281c21e330eec156098c14f4e685 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 25 Jan 2007 17:35:40 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 0e2348a..4c9ef7f 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 a6ba0fa..bfb7188 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 && -- cgit v0.10.2-6-g49f6 From d3a840dc74d2098c31aac1b89093d847e1d33dd8 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 26 Jan 2007 01:32:45 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 4c9ef7f..1d448e7 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 5355243..3dc4de2 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 6323c7e..c668dd1 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 -- cgit v0.10.2-6-g49f6 From 6e8548cca888205a99773a18a73533b2b8dc651d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 27 Jan 2007 01:32:00 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 1d448e7..e771bf5 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 @@ $log_entry hashref as returned by libsvn_log_entry() 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 -- cgit v0.10.2-6-g49f6 From 6139535436d7ec9808fca75821f14d4d5061f343 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 27 Jan 2007 14:33:08 -0800 Subject: git-svn: simplify usage of the SVN::Git::Editor interface Signed-off-by: Eric Wong diff --git a/git-svn.perl b/git-svn.perl index e771bf5..4f7ebaf 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 @@ use Carp qw/croak/; use IO::File; sub new { - my $class = shift; - my $git_svn = shift; - my $self = SVN::Delta::Editor->new(@_); + my ($class, $opts) = @_; + foreach (qw/svn_path r ra tree_a tree_b log editor_cb/) { + die "$_ required!\n" unless (defined $opts->{$_}); + } + + 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 mods url types r/) { - die "$_ required!\n" unless (defined $git_svn->{$_}); - $self->{$_} = $git_svn->{$_}; + foreach (qw/svn_path r tree_a tree_b/) { + $self->{$_} = $opts->{$_}; } - $self->{pool} = SVN::Pool->new; + $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 { -- cgit v0.10.2-6-g49f6 From 21819a370839fdae818975967cef384510e4a8cd Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 27 Jan 2007 14:38:10 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 4f7ebaf..7249d6f 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; } -- cgit v0.10.2-6-g49f6 From 0af9c9f94ae8a327536679ec1976df65ecd64b6e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 27 Jan 2007 22:28:56 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 7249d6f..5d398ee 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; -- cgit v0.10.2-6-g49f6 From 2fa6a23efb200cea814add88ce1d1d193ba83860 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 28 Jan 2007 04:02:01 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 5d398ee..36e5c57 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 $!; -- cgit v0.10.2-6-g49f6 From 2b27f6c8847ebee631e7ad17ac9986e461d7674b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 28 Jan 2007 04:59:05 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 36e5c57..dcc7fd4 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 bfb7188..6d243f8 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 -- cgit v0.10.2-6-g49f6 From ce2a0f2f9d9fd8eee8eaa1f24a60bdafade80c24 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 28 Jan 2007 21:34:23 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index dcc7fd4..4f7938a 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 6d243f8..0f4e736 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' " -- cgit v0.10.2-6-g49f6 From 24e22aa8a5762b11a8025c6ad82c945828667d0f Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 29 Jan 2007 00:07:49 -0800 Subject: git-svn: cleanup: move editor-specific variables into the editor namespace Also removed some unused/redundant functions. Signed-off-by: Eric Wong diff --git a/git-svn.perl b/git-svn.perl index 4f7938a..20c6fc7 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -8,8 +8,8 @@ use vars qw/ $AUTHOR $VERSION $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 @@ my %multi_opts = ( 'trunk|T=s' => \$_trunk, '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 { -- cgit v0.10.2-6-g49f6 From 90c1b15da303fa40457e0a9e5c11a57adbfa1879 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 29 Jan 2007 16:27:08 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 20c6fc7..e387504 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1577,6 +1577,7 @@ use strict; 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 @@ use strict; 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; } -- cgit v0.10.2-6-g49f6 From f7c3fc4a269ad4f12cc9d97d70e46ba3d5dfdb79 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 29 Jan 2007 18:34:55 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index e387504..bbdaeea 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 { -- cgit v0.10.2-6-g49f6 From 5d3b7cd5fe5d0410915cc68b641415901e1b113e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 29 Jan 2007 19:16:01 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index bbdaeea..4e357df 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -870,7 +870,7 @@ sub refname { "refs/remotes/$_[0]->{ref_id}" } 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 @@ use vars qw/@ISA $config_dir/; 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}; -- cgit v0.10.2-6-g49f6 From 289370578ca5833641fbb59813173ac6db1986d1 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 30 Jan 2007 00:35:18 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 4e357df..b0248c9 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 0f4e736..dcec16b 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 -- cgit v0.10.2-6-g49f6 From f0ecca1041aff2e1398edcdaf5f0ce08a96f5f0f Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 30 Jan 2007 13:11:14 -0800 Subject: 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 diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 6daba24..22dd7b2 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 b0248c9..845f22a 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -106,9 +106,6 @@ my %cmd = ( { '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 @@ usage(0) if $_help; 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 -- cgit v0.10.2-6-g49f6 From 8a603774def11e5a90ae2dbbc0c8d4abacdb2d99 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 31 Jan 2007 02:45:50 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 845f22a..2afa253 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 dcec16b..41b9c19 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 -- cgit v0.10.2-6-g49f6 From 9760adccccc0cc4dccc2f28765611550db640ceb Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 31 Jan 2007 03:06:56 -0800 Subject: 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 diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 22dd7b2..2914042 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 2afa253..cc5736d 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 @@ $ENV{GIT_DIR} ||= '.git'; $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 @@ $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, $_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 @@ for (my $i = 0; $i < @ARGV; $i++) { 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; } -- cgit v0.10.2-6-g49f6 From c7eba7163b452840c8492b9ad87846b44cc98ea7 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 31 Jan 2007 03:45:28 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index cc5736d..b2f86e8 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; -- cgit v0.10.2-6-g49f6 From d4eff2bda5fc28559e96d62604ecaf78a4ff806b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 30 Jan 2007 14:04:22 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index b2f86e8..efc5515 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; - - 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; + 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) ] }); + + 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); -- cgit v0.10.2-6-g49f6 From 47a0b75e01876b6e138d78c9504eebedd45c1283 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 31 Jan 2007 05:13:30 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index efc5515..ddb0382 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 $!; -- cgit v0.10.2-6-g49f6 From ecc712ddc41999e5f082cb69406d30caa062c6b9 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 31 Jan 2007 12:28:10 -0800 Subject: git-svn: re-enable repacking flags Signed-off-by: Eric Wong diff --git a/git-svn.perl b/git-svn.perl index ddb0382..1fd6552 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -52,7 +52,6 @@ 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, - $_repack, $_repack_nr, $_repack_flags, $_message, $_file, $_template, $_shared, $_version, @@ -64,10 +63,11 @@ my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username, '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 @@ load_authors() if $_authors; 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; } -- cgit v0.10.2-6-g49f6 From 373274f978a48b62549f20059bff630d85533533 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 31 Jan 2007 13:54:23 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 1fd6552..2206f1b 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -631,6 +631,7 @@ 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 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 { -- cgit v0.10.2-6-g49f6 From 9c93fee51e26d5db82414317fa169294f3fa94b0 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 31 Jan 2007 17:22:31 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 2206f1b..b1d91fa 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; -- cgit v0.10.2-6-g49f6 From ce4b4af7ff36d4e4999da937dffd2f9a3a420277 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 31 Jan 2007 17:57:36 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index b1d91fa..1e3a3c0 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; -- cgit v0.10.2-6-g49f6 From d8115c5104dbee29433a7f33a3e0d3e1738a581e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 1 Feb 2007 03:30:31 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 1e3a3c0..a2db73f 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 41b9c19..7c852c1 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$'\`\" " -- cgit v0.10.2-6-g49f6 From 88cf4107eb70cdcdc226f2385a3ee54fb428c41d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 1 Feb 2007 03:59:07 -0800 Subject: git-svn: save paths to tags/branches with for future reuse Signed-off-by: Eric Wong diff --git a/git-svn.perl b/git-svn.perl index a2db73f..ad2ef53 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); } } -- cgit v0.10.2-6-g49f6 From 471bc000528cf49928dae8872609b7fefc0c59ee Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 1 Feb 2007 04:06:27 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index ad2ef53..de14ed4 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; -- cgit v0.10.2-6-g49f6 From ef70de968509c447f5c02f4ba99f1cf0cadf5c1f Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 1 Feb 2007 04:12:41 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index de14ed4..58d0600 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; -- cgit v0.10.2-6-g49f6 From 502c1bf629154b4a248105b10346a06a6ff07387 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 1 Feb 2007 04:26:00 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 58d0600..8c24012 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, -- cgit v0.10.2-6-g49f6 From 9fa00b655cfd67bf344668a0d913f90ec9a8141d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 3 Feb 2007 12:49:48 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 8c24012..66b4c20 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -10,7 +10,7 @@ $AUTHOR = 'Eric Wong '; $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 3dc4de2..8b6c8ff 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 7c852c1..eebb849 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 f6d84ba..0fbfd26 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 && -- cgit v0.10.2-6-g49f6 From 4bb9ed0466e9ca6b72b3d9c454a743aea862b1f4 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 3 Feb 2007 13:29:17 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 66b4c20..6874eab 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 0fbfd26..8376429 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: -- cgit v0.10.2-6-g49f6 From fbcc1737d61f2b83846c59a7cc0d820e09833350 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 6 Feb 2007 18:35:30 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 6874eab..d0bde71 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); } } -- cgit v0.10.2-6-g49f6 From d2ae14346c71d57c57dc7b1c1f8272c09f51ccf9 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 7 Feb 2007 11:50:16 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index d0bde71..5e1a64c 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) { -- cgit v0.10.2-6-g49f6 From e518192f3be92097ba550098dbb69d769ca18429 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 8 Feb 2007 12:53:57 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 5e1a64c..21d5305 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 @@ 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"; + 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; -- cgit v0.10.2-6-g49f6 From b9dffd8cad737a07d6a05503318c6746ac593f9c Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 9 Feb 2007 01:28:30 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 21d5305..cd35efe 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 eebb849..f5b7e5e 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 " -- cgit v0.10.2-6-g49f6 From d542aedb9424872474e896a6d20407745d4bd627 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 9 Feb 2007 02:19:41 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index cd35efe..819d25e 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; -- cgit v0.10.2-6-g49f6 From e20bea654513aa7eba7e356994fa56cc90bb5b6d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 9 Feb 2007 02:32:48 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 819d25e..c8a1df3 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; } -- cgit v0.10.2-6-g49f6 From 0bed5eaa0edc3a2dfa0d5910ff1fb280539b242d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 9 Feb 2007 02:45:03 -0800 Subject: 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 diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 2914042..50dc6ac 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 c8a1df3..b9bacc3 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -57,11 +57,11 @@ my ($_stdin, $_help, $_edit, $_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 f5b7e5e..53f5a92 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 &' -- cgit v0.10.2-6-g49f6 From 4e9f6cc78e5d955bd0faffe76ae9aea6590189f1 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 9 Feb 2007 12:17:57 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index b9bacc3..7664b38 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; } -- cgit v0.10.2-6-g49f6 From 9e3cdbd4f2e02bf63bfaa8f6e2747601f117cf2d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 9 Feb 2007 12:23:47 -0800 Subject: git-svn: correctly handle the -q flag in SVN::Git::Fetcher Signed-off-by: Eric Wong diff --git a/git-svn.perl b/git-svn.perl index 7664b38..ed363e9 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; } -- cgit v0.10.2-6-g49f6 From 74a81227f95b52b1c3f7ac7ba84ac1a6e1708995 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 10 Feb 2007 13:28:50 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index ed363e9..50b7dcf 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 @@ my $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 0000000..47cccdfd --- /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 -- cgit v0.10.2-6-g49f6 From 490f49ea5899b7aacfb82c0ed5639d722a56704a Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 10 Feb 2007 13:58:33 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 50b7dcf..7e1a655 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 { -- cgit v0.10.2-6-g49f6 From 8a49ee9759f72ba7c61e035a2ca4b10d8a51c94e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 10 Feb 2007 20:46:50 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 7e1a655..23e1d42 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -64,6 +64,7 @@ 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, '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; } -- cgit v0.10.2-6-g49f6 From 91b03282b586cc521f9b009ea8273444fb58f2f1 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 11 Feb 2007 00:51:33 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 23e1d42..becf2e0 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"; -- cgit v0.10.2-6-g49f6 From 93f2689ccdf7b62864aac40097bfd51328fae5b7 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 11 Feb 2007 01:20:26 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index becf2e0..b62cc06 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 -- cgit v0.10.2-6-g49f6 From 97ae091169b233ecd80eb5ef2da80145f8c724f7 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 11 Feb 2007 15:21:24 -0800 Subject: 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 diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 50dc6ac..d45283a 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. - -config key: svn.nometadata + The 'git-svn log' command will not work on repositories using + this, either. Using this conflicts with the 'useSvmProps' + option for (hopefully) obvious reasons. + +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 b62cc06..fdecffb 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -63,8 +63,8 @@ my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username, 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", -- cgit v0.10.2-6-g49f6 From 26a62d57a27407132d48e91b3c8f455a5fb22e4b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 12 Feb 2007 13:25:25 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index fdecffb..beebe3d 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,42 +1628,59 @@ 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 ($fh, $rev, $commit) = @_; + my $offset = $rev * 41; + # assume that append is the common case: + seek $fh, 0, 2 or croak $!; + my $pos = tell $fh; + if ($pos < $offset) { + 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 $!; +} + +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) = @_; - 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 ($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: ", + "Failed to copy: ", "$db => $db_lock ($!)\n"; } else { rename $db, $db_lock or die "rev_db_set(@_): ", - "Failed to rename: ", + "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 $!; - my $pos = tell $fh; - if ($pos < $offset) { - 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 $!; + 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 8376429..9f107ad 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 -- cgit v0.10.2-6-g49f6 From c3560e535c67b4084852da00507ff4b7fdf98ffc Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 12 Feb 2007 16:03:32 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index beebe3d..b7e46e5 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; -- cgit v0.10.2-6-g49f6 From db03cd24a155a727349a47ce0e5ba3f4c4032cb8 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 13 Feb 2007 00:38:02 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index b7e46e5..d556912 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; + + 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); - 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}"; + chomp($src, $mirror, $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{!$}{}; + $src =~ s{/?!$}{$mirror}; + $src =~ s{/+$}{}; # no trailing slashes please $src =~ s{(^[a-z\+]*://)[^/@]*@}{$1}; - 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"; + 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; } -- cgit v0.10.2-6-g49f6 From 2edb9c5cf98e1a65c775ede6fc5b10a15bb94384 Mon Sep 17 00:00:00 2001 From: "sam@vilain.net" Date: Tue, 5 Dec 2006 16:17:38 +1100 Subject: 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 diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index 67d08cf..27ad3b7 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 0000000..0e0ba3d --- /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 -- cgit v0.10.2-6-g49f6 From a8ae26235ced15d8ce129a8ff72c558b8a567813 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 13 Feb 2007 14:22:11 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index d556912..09c0aba 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+$//; -- cgit v0.10.2-6-g49f6 From ce207c7ad1604c6afd5014051641e40f346a59c6 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 13 Feb 2007 15:56:08 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 09c0aba..66653f9 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>) { -- cgit v0.10.2-6-g49f6 From 3bc718ba66f8b101b4017e778138660d66829312 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 13 Feb 2007 17:09:40 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 66653f9..fb2c864 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); -- cgit v0.10.2-6-g49f6 From ccb6b6f5b50bb32f90222a3e801a1901bf0b5657 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 13 Feb 2007 17:38:58 -0800 Subject: 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 diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index 8b6c8ff..7dcfc7e 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 53f5a92..bd4f366 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 9f107ad..d26c355 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 47cccdfd..be21fc1 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 0e0ba3d..7e42151 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 && -- cgit v0.10.2-6-g49f6 From e98671e5c2067a39759c8f08b79c260a9f0b2771 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 02:21:19 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index fb2c864..3eed62f 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -53,7 +53,7 @@ $sha1_short = qr/[a-f\d]{4,40}/; 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 @@ my %cmt_opts = ( 'edit|e' => \$_edit, 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 @@ my %cmd = ( '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); } -- cgit v0.10.2-6-g49f6 From dadc6d2a0904e55ac5a5a810dffac4d44fff0b66 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 12:27:41 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 3eed62f..b2931cd 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -71,10 +71,10 @@ my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent, %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 @@ my %cmd = ( 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 @@ my %cmd = ( { '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 @@ Usage: $0 [options] [arguments]\n 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 d26c355..a20038b 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 7e42151..1e1b97b 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" -- cgit v0.10.2-6-g49f6 From 28710f74ea1f1d8a46c867ddd471dae3d7c3a664 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 13:32:21 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index b2931cd..24ca308 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 be21fc1..db4344c 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 -- cgit v0.10.2-6-g49f6 From b4d57e5ea3349a6bf63c3626f6fb36bac11be3c0 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 15:10:44 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 24ca308..f4573ed 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", -- cgit v0.10.2-6-g49f6 From 6af1db447b10c03db4c04a55000efaa9aad38caa Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 16:04:10 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index f4573ed..8a80f81 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -12,6 +12,7 @@ $VERSION = '@@GIT_VERSION@@'; $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 @@ my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent, '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"; -- cgit v0.10.2-6-g49f6 From e8d120bd5a7e09b24c6fa2245cf429e3411028ee Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 16:29:52 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 8a80f81..ace3102 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); -- cgit v0.10.2-6-g49f6 From 7447b4bc837d2f73fb4cd34f2a44a0cb120c5c39 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 18:38:46 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index ace3102..201418e 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; -- cgit v0.10.2-6-g49f6 From 60d9c97adf96533e4f02d3fc2fecec998104e8ea Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 18:47:16 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 201418e..bfe5d6b 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 -- cgit v0.10.2-6-g49f6 From a836a0e1729d1758b4085cd07fc79cb9acb64908 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 14 Feb 2007 19:34:56 -0800 Subject: 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 diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index d45283a..ba3f7ce 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. -'fetch':: +-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) -Fetch unfetched revisions from the Subversion URL we are -tracking. refs/remotes/git-svn will be updated to the -latest revision. +--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. -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':: + + 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 bfe5d6b..31e536c 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -114,7 +114,8 @@ my %cmd = ( # 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 @@ Usage: $0 [options] [arguments]\n 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 ? -- cgit v0.10.2-6-g49f6 From 488a63ec233ce7edc5fd70172c196628b1d6a82b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 15 Feb 2007 00:40:42 -0800 Subject: git-svn: add support for --stat in the log command Signed-off-by: Eric Wong diff --git a/git-svn.perl b/git-svn.perl index 31e536c..2152bf3 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) { -- cgit v0.10.2-6-g49f6 From 1e889ef36c45b5554f7e317493ed3f4f901f8d9f Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 16 Feb 2007 01:45:13 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 2152bf3..7ffbf64 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -56,7 +56,7 @@ my ($_stdin, $_help, $_edit, $_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 @@ my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent, '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 @@ eval { $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); } -- cgit v0.10.2-6-g49f6 From 905f8b7dfc2c520b91f418ab0f2aecb1c371fbe4 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 16 Feb 2007 03:22:40 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 7ffbf64..eca08bd 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -56,7 +56,7 @@ my ($_stdin, $_help, $_edit, $_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 @@ my %cmt_opts = ( 'edit|e' => \$_edit, 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 @@ my %cmd = ( '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 @@ my %cmd = ( '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); -- cgit v0.10.2-6-g49f6 From d6d3346babaf19864ea104b0140279e62f32f7e3 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 16 Feb 2007 04:05:33 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index eca08bd..d7fc9aa 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"; -- cgit v0.10.2-6-g49f6 From b7e5348c7f6369554813207a24cf841f48bd8b23 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 16 Feb 2007 04:09:28 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index d7fc9aa..571259f 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 a20038b..dc2afda 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 && -- cgit v0.10.2-6-g49f6 From 0425ea90889f967c3966ace3e5a85b9a5a44c358 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 16 Feb 2007 18:45:01 -0800 Subject: git-svn: add 'clone' command, an alias for init + fetch Signed-off-by: Eric Wong diff --git a/git-svn.perl b/git-svn.perl index 571259f..2cc7c33 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -90,6 +90,9 @@ my %cmd = ( { '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 @@ usage(0) if $_help; 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(@_); -- cgit v0.10.2-6-g49f6 From aea736cc6db64219b946adb4ca77f5d17bc7ab77 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 16 Feb 2007 19:15:21 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 2cc7c33..3e48c56 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; } -- cgit v0.10.2-6-g49f6 From 62e349d235ecbb20c5338de5d4cbff9ce5c6aa66 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 16 Feb 2007 19:57:29 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 3e48c56..7563eea 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -66,6 +66,7 @@ my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent, '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; -- cgit v0.10.2-6-g49f6 From befc9adc0ced7d3e1c1316d6420007357d50b202 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 17 Feb 2007 02:53:07 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 7563eea..1bcf058 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -164,7 +164,9 @@ 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, - '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 @@ use strict; 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 1e1b97b..0000000 --- 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 0000000..9db0d8f --- /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 0000000..cc799c2 --- /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 + + -- cgit v0.10.2-6-g49f6 From e2b36f6018062cfa44b6952ce73c0609db0af240 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 18 Feb 2007 01:30:35 -0800 Subject: 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 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 0000000..483d7f8 --- /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 0000000..a9a46ee --- /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 + + -- cgit v0.10.2-6-g49f6 From a81ed0b63edb0d473e17e078de2269b735156a79 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 18 Feb 2007 02:10:51 -0800 Subject: 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 diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index ba3f7ce..bd163cf 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 ------------- -- cgit v0.10.2-6-g49f6 From 0dfaf0a4e1905a9137d3f2f691620529aeb3b4fa Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sun, 18 Feb 2007 02:34:09 -0800 Subject: 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 diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index bd163cf..da68f6d 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 1bcf058..dc78dcf 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -75,9 +75,14 @@ my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent, %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 { -- cgit v0.10.2-6-g49f6 From 1a97a506043691741f25e8967e76123c1114d1fb Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 20 Feb 2007 00:43:19 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index dc78dcf..b4e8966 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; -- cgit v0.10.2-6-g49f6 From 5253dc33b713e3de63a25305bfc5e966999a0fbe Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Tue, 20 Feb 2007 01:36:30 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index b4e8966..a6d98f1 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -9,6 +9,7 @@ use vars qw/ $AUTHOR $VERSION $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 @@ usage(0) if $_help; 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(); } -- cgit v0.10.2-6-g49f6 From 18ea92bd818d38c808329abf77ee8f1ca156f446 Mon Sep 17 00:00:00 2001 From: Sam Vilain Date: Fri, 23 Feb 2007 12:32:29 +1300 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index a6d98f1..ea5afb7 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; -- cgit v0.10.2-6-g49f6 From a0d7fe3fcd790876e510c11459df95a0d595e2ad Mon Sep 17 00:00:00 2001 From: Sam Vilain Date: Fri, 23 Feb 2007 12:32:30 +1300 Subject: 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 diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index da68f6d..cf094ca 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 -- cgit v0.10.2-6-g49f6 From f30603fcf37f44942a2173386c0f580a508158df Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 23 Feb 2007 01:26:26 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index ea5afb7..a5c6eb9 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: -- cgit v0.10.2-6-g49f6 From e2c475d91cc65b110a20c9836142a4a7e608a87c Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 23 Feb 2007 01:57:40 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index a5c6eb9..2bd70a1 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([ -- cgit v0.10.2-6-g49f6 From 2e5e24803fc92a1da5b941495dc22f888fc612b6 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 23 Feb 2007 02:21:59 -0800 Subject: 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 diff --git a/git-svn.perl b/git-svn.perl index 2bd70a1..41961b5 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; -- cgit v0.10.2-6-g49f6 From 509b4d73b26bb442fd409bbca3772ba22875d310 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 23 Feb 2007 03:11:52 -0800 Subject: .mailmap maintenance after pulling from git-svn Signed-off-by: Junio C Hamano diff --git a/.mailmap b/.mailmap index c7a3a75..3a624ea 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 -- cgit v0.10.2-6-g49f6 From 8ab40a20053aa4a0f8d92d08ece88ff09b771435 Mon Sep 17 00:00:00 2001 From: "Dmitry V. Levin" Date: Fri, 23 Feb 2007 20:12:33 +0300 Subject: 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 diff --git a/builtin-show-ref.c b/builtin-show-ref.c index 853f13f..75211e6 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) -- cgit v0.10.2-6-g49f6 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: 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 diff --git a/Documentation/config.txt b/Documentation/config.txt index 4a22a00..9fec769 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 -- cgit v0.10.2-6-g49f6 From c06d2daa12f72f175e3569bbb2a5f25e5f134f5e Mon Sep 17 00:00:00 2001 From: Robin Rosenberg Date: Fri, 23 Feb 2007 23:27:58 +0100 Subject: 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 diff --git a/builtin-log.c b/builtin-log.c index af2de54..a5e4b62 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; -- cgit v0.10.2-6-g49f6 From b1440cc8060e79f885691ebeae7ce033bd606a7c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 24 Feb 2007 01:05:27 -0800 Subject: 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 diff --git a/git-am.sh b/git-am.sh index 6db9cb5..2c73d11 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" \ -- cgit v0.10.2-6-g49f6 From 5089277718503a4de7817b5f6754cb03116d5524 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 23 Feb 2007 03:44:30 -0800 Subject: diff-patch: Avoid emitting double-slashes in textual patch. Signed-off-by: Junio C Hamano diff --git a/diff.c b/diff.c index 13b9b6c..b8a90e9 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); -- cgit v0.10.2-6-g49f6 From 92446aba47b0e0db28f7b858ea387efcca30ab44 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 25 Feb 2007 02:18:11 -0500 Subject: 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 diff --git a/CREDITS-GEN b/CREDITS-GEN index da2c076..d1b0f86 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 + -- cgit v0.10.2-6-g49f6 From ab242f809a9e06ebf935c0512155fc5661ab84ab Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 25 Feb 2007 01:24:34 -0800 Subject: 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 diff --git a/builtin-rerere.c b/builtin-rerere.c index 318d959..ac0bf33 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; -- cgit v0.10.2-6-g49f6 From 128917274943bd0e2dea69862c5a9893a962e350 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 25 Feb 2007 01:29:43 -0800 Subject: 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 diff --git a/builtin-rerere.c b/builtin-rerere.c index ac0bf33..58c5fed 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; } -- cgit v0.10.2-6-g49f6 From ffa84ffb77b06f9793967fb4f4dd5c946da8e341 Mon Sep 17 00:00:00 2001 From: Roland Dreier Date: Sun, 25 Feb 2007 09:34:27 -0800 Subject: 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 diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 3824ee3..9713882 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; } -- cgit v0.10.2-6-g49f6 From d2dc6222d4e2f7fa5efc82175d14d60d7b804687 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 25 Feb 2007 10:53:42 -0800 Subject: Add Release Notes to prepare for 1.5.0.2 Signed-off-by: Junio C Hamano diff --git a/Documentation/RelNotes-1.5.0.2.txt b/Documentation/RelNotes-1.5.0.2.txt new file mode 100644 index 0000000..4dc1344 --- /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 63941cd..5308f6b 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 -- cgit v0.10.2-6-g49f6 From 17e48368751ae8df7ed0332ae8ef900ce983fce6 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 24 Feb 2007 18:18:22 -0800 Subject: 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 diff --git a/.gitignore b/.gitignore index f15155d..eb8a1f8 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 e51b448..8a42be9 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 0000000..90da448 --- /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; +} -- cgit v0.10.2-6-g49f6 From 56cf9806a97f5fe9a91c38aa3eb8cec0c76480e6 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 24 Feb 2007 16:59:52 -0800 Subject: 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 diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index 27ad3b7..f6fe78c 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 c571a1b..639d45f 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. - - 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)" - - touch -m -t "$predate1" $rr/preimage - touch -m -t "$predate2" $rr2/preimage - - test_expect_success 'garbage collection (part1)' 'git rerere gc' - - test_expect_success 'young records still live' \ - "test -f $rr/preimage -a -f $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 'old records rest in peace' \ - "test ! -f $rr/preimage -a ! -f $rr2/preimage" - ;; -esac +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)) + +test-chmtime =$almost_60_days_ago $rr/preimage +test-chmtime =$almost_15_days_ago $rr2/preimage + +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-chmtime =$just_over_60_days_ago $rr/preimage +test-chmtime =$just_over_15_days_ago $rr2/preimage + +test_expect_success 'garbage collection (part2)' 'git rerere gc' + +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 a403fe0..c075474 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" -- cgit v0.10.2-6-g49f6 From 6c09c451389ade6477d98b05d277aab0dd2f272e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 24 Feb 2007 22:26:33 -0800 Subject: 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 diff --git a/builtin-diff.c b/builtin-diff.c index c387ebb..67f4932 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; } } -- cgit v0.10.2-6-g49f6 From 308efc10d890265a4c62a8b58f025aaf8a1f244d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 25 Feb 2007 18:17:15 -0800 Subject: 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 diff --git a/merge-index.c b/merge-index.c index a9983dd..7027d78 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); -- cgit v0.10.2-6-g49f6 From 17cd29b25c0f480a3c6e1dd9617d9c6046e3c280 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 25 Feb 2007 18:42:07 -0800 Subject: 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 diff --git a/merge-recursive.c b/merge-recursive.c index 5898942..397a7ad 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'", -- cgit v0.10.2-6-g49f6 From 4fc970c43884e6215c13d6944a2def1eb2134ed5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 25 Feb 2007 22:24:47 -0800 Subject: 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 diff --git a/combine-diff.c b/combine-diff.c index a5f2c8d..6b7c6be 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 556d534..60c0fa6 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]; -- cgit v0.10.2-6-g49f6 From c5ddca1fff44896b7e94801da75392ccc234060c Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 25 Feb 2007 23:26:11 -0800 Subject: Documentation: describe "-f/-t/-m" options to "git-remote add" Signed-off-by: Junio C Hamano diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index a60c31a..a2ff8f0 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':: -- cgit v0.10.2-6-g49f6 From 4e5104c1fc42e440b9f086d428157d45897e1273 Mon Sep 17 00:00:00 2001 From: Pavel Roskin Date: Wed, 21 Feb 2007 00:03:36 -0500 Subject: 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 diff --git a/git-remote.perl b/git-remote.perl index c56c5a8..670bafb 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); } } -- cgit v0.10.2-6-g49f6 From 0d9b9ab1284ce125fd49cf7dbf4d28e0540cf035 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 25 Feb 2007 23:58:50 -0800 Subject: GIT 1.5.0.2 Signed-off-by: Junio C Hamano diff --git a/Documentation/RelNotes-1.5.0.2.txt b/Documentation/RelNotes-1.5.0.2.txt index 4dc1344..b061e50 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: +* Assorted git-gui fixes. diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 9133a00..32a9422 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=' ' -- cgit v0.10.2-6-g49f6 From 047f636d903c773e237f684826cc68c79a1a0075 Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V" Date: Sat, 24 Feb 2007 21:02:56 +0530 Subject: 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 diff --git a/Documentation/config.txt b/Documentation/config.txt index 6309d89..d2b4a05 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]. -- cgit v0.10.2-6-g49f6 From c260d790c85c07a5f50235f664c36725deedfb10 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 26 Feb 2007 01:16:01 -0800 Subject: Documentation: link in 1.5.0.2 material to the top documentation page. Signed-off-by: Junio C Hamano diff --git a/Documentation/git.txt b/Documentation/git.txt index aa3acc0..9a74747 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] -- cgit v0.10.2-6-g49f6