diff options
Diffstat (limited to 'contrib')
-rw-r--r-- | contrib/blameview/README | 9 | ||||
-rwxr-xr-x | contrib/blameview/blameview.perl | 155 | ||||
-rw-r--r-- | contrib/completion/git-completion.bash | 181 | ||||
-rw-r--r-- | contrib/completion/git-completion.zsh | 27 | ||||
-rw-r--r-- | contrib/completion/git-prompt.sh | 133 | ||||
-rw-r--r-- | contrib/continuous/cidaemon | 503 | ||||
-rw-r--r-- | contrib/continuous/post-receive-cinotify | 104 | ||||
-rw-r--r-- | contrib/credential/osxkeychain/git-credential-osxkeychain.c | 12 | ||||
-rwxr-xr-x | contrib/mw-to-git/git-remote-mediawiki.perl | 103 | ||||
-rw-r--r-- | contrib/patches/docbook-xsl-manpages-charmap.patch | 21 | ||||
-rwxr-xr-x | contrib/remote-helpers/git-remote-bzr | 117 | ||||
-rwxr-xr-x | contrib/remote-helpers/git-remote-hg | 510 | ||||
-rwxr-xr-x | contrib/remote-helpers/test-bzr.sh | 492 | ||||
-rwxr-xr-x | contrib/remote-helpers/test-hg-bidi.sh | 35 | ||||
-rwxr-xr-x | contrib/remote-helpers/test-hg-hg-git.sh | 55 | ||||
-rwxr-xr-x | contrib/remote-helpers/test-hg.sh | 725 | ||||
-rwxr-xr-x | contrib/subtree/git-subtree.sh | 5 |
17 files changed, 1560 insertions, 1627 deletions
diff --git a/contrib/blameview/README b/contrib/blameview/README deleted file mode 100644 index fada5ce909..0000000000 --- a/contrib/blameview/README +++ /dev/null @@ -1,9 +0,0 @@ -This is a sample program to use 'git-blame --incremental', based -on this message. - -From: Jeff King <peff@peff.net> -Subject: Re: More precise tag following -To: Linus Torvalds <torvalds@linux-foundation.org> -Cc: git@vger.kernel.org -Date: Sat, 27 Jan 2007 18:52:38 -0500 -Message-ID: <20070127235238.GA28706@coredump.intra.peff.net> diff --git a/contrib/blameview/blameview.perl b/contrib/blameview/blameview.perl deleted file mode 100755 index 1dec00137b..0000000000 --- a/contrib/blameview/blameview.perl +++ /dev/null @@ -1,155 +0,0 @@ -#!/usr/bin/perl - -use Gtk2 -init; -use Gtk2::SimpleList; - -my $hash; -my $fn; -if ( @ARGV == 1 ) { - $hash = "HEAD"; - $fn = shift; -} elsif ( @ARGV == 2 ) { - $hash = shift; - $fn = shift; -} else { - die "Usage blameview [<rev>] <filename>"; -} - -Gtk2::Rc->parse_string(<<'EOS'); -style "treeview_style" -{ - GtkTreeView::vertical-separator = 0 -} -class "GtkTreeView" style "treeview_style" -EOS - -my $window = Gtk2::Window->new('toplevel'); -$window->signal_connect(destroy => sub { Gtk2->main_quit }); -my $vpan = Gtk2::VPaned->new(); -$window->add($vpan); -my $scrolled_window = Gtk2::ScrolledWindow->new; -$vpan->pack1($scrolled_window, 1, 1); -my $fileview = Gtk2::SimpleList->new( - 'Commit' => 'text', - 'FileLine' => 'text', - 'Data' => 'text' -); -$scrolled_window->add($fileview); -$fileview->get_column(0)->set_spacing(0); -$fileview->set_size_request(1024, 768); -$fileview->set_rules_hint(1); -$fileview->signal_connect (row_activated => sub { - my ($sl, $path, $column) = @_; - my $row_ref = $sl->get_row_data_from_path ($path); - system("blameview @$row_ref[0]~1 $fn &"); - }); - -my $commitwindow = Gtk2::ScrolledWindow->new(); -$commitwindow->set_policy ('GTK_POLICY_AUTOMATIC','GTK_POLICY_AUTOMATIC'); -$vpan->pack2($commitwindow, 1, 1); -my $commit_text = Gtk2::TextView->new(); -my $commit_buffer = Gtk2::TextBuffer->new(); -$commit_text->set_buffer($commit_buffer); -$commitwindow->add($commit_text); - -$fileview->signal_connect (cursor_changed => sub { - my ($sl) = @_; - my ($path, $focus_column) = $sl->get_cursor(); - my $row_ref = $sl->get_row_data_from_path ($path); - my $c_fh; - open($c_fh, '-|', "git cat-file commit @$row_ref[0]") - or die "unable to find commit @$row_ref[0]"; - my @buffer = <$c_fh>; - $commit_buffer->set_text("@buffer"); - close($c_fh); - }); - -my $fh; -open($fh, '-|', "git cat-file blob $hash:$fn") - or die "unable to open $fn: $!"; - -while(<$fh>) { - chomp; - $fileview->{data}->[$.] = ['HEAD', "$fn:$.", $_]; -} - -my $blame; -open($blame, '-|', qw(git blame --incremental --), $fn, $hash) - or die "cannot start git-blame $fn"; - -Glib::IO->add_watch(fileno($blame), 'in', \&read_blame_line); - -$window->show_all; -Gtk2->main; -exit 0; - -my %commitinfo = (); - -sub flush_blame_line { - my ($attr) = @_; - - return unless defined $attr; - - my ($commit, $s_lno, $lno, $cnt) = - @{$attr}{qw(COMMIT S_LNO LNO CNT)}; - - my ($filename, $author, $author_time, $author_tz) = - @{$commitinfo{$commit}}{qw(FILENAME AUTHOR AUTHOR-TIME AUTHOR-TZ)}; - my $info = $author . ' ' . format_time($author_time, $author_tz); - - for(my $i = 0; $i < $cnt; $i++) { - @{$fileview->{data}->[$lno+$i-1]}[0,1,2] = - (substr($commit, 0, 8), $filename . ':' . ($s_lno+$i)); - } -} - -my $buf; -my $current; -sub read_blame_line { - - my $r = sysread($blame, $buf, 1024, length($buf)); - die "I/O error" unless defined $r; - - if ($r == 0) { - flush_blame_line($current); - $current = undef; - return 0; - } - - while ($buf =~ s/([^\n]*)\n//) { - my $line = $1; - - if (($commit, $s_lno, $lno, $cnt) = - ($line =~ /^([0-9a-f]{40}) (\d+) (\d+) (\d+)$/)) { - flush_blame_line($current); - $current = +{ - COMMIT => $1, - S_LNO => $2, - LNO => $3, - CNT => $4, - }; - next; - } - - # extended attribute values - if ($line =~ /^(author|author-mail|author-time|author-tz|committer|committer-mail|committer-time|committer-tz|summary|filename) (.*)$/) { - my $commit = $current->{COMMIT}; - $commitinfo{$commit}{uc($1)} = $2; - next; - } - } - return 1; -} - -sub format_time { - my $time = shift; - my $tz = shift; - - my $minutes = $tz < 0 ? 0-$tz : $tz; - $minutes = ($minutes / 100)*60 + ($minutes % 100); - $minutes = $tz < 0 ? 0-$minutes : $minutes; - $time += $minutes * 60; - my @t = gmtime($time); - return sprintf('%04d-%02d-%02d %02d:%02d:%02d %s', - $t[5] + 1900, @t[4,3,2,1,0], $tz); -} diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index ecf58e0328..6c3bafeea5 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -252,106 +252,50 @@ __gitcomp_file () # since tilde expansion is not applied. # This means that COMPREPLY will be empty and Bash default # completion will be used. - COMPREPLY=($(compgen -P "${2-}" -W "$1" -- "${3-$cur}")) + __gitcompadd "$1" "${2-}" "${3-$cur}" "" - # Tell Bash that compspec generates filenames. - compopt -o filenames 2>/dev/null + # use a hack to enable file mode in bash < 4 + compopt -o filenames +o nospace 2>/dev/null || + compgen -f /non-existing-dir/ > /dev/null } -__git_index_file_list_filter_compat () -{ - local path - - while read -r path; do - case "$path" in - ?*/*) echo "${path%%/*}/" ;; - *) echo "$path" ;; - esac - done -} - -__git_index_file_list_filter_bash () -{ - local path - - while read -r path; do - case "$path" in - ?*/*) - # XXX if we append a slash to directory names when using - # `compopt -o filenames`, Bash will append another slash. - # This is pretty stupid, and this the reason why we have to - # define a compatible version for this function. - echo "${path%%/*}" ;; - *) - echo "$path" ;; - esac - done -} - -# Process path list returned by "ls-files" and "diff-index --name-only" -# commands, in order to list only file names relative to a specified -# directory, and append a slash to directory names. -__git_index_file_list_filter () -{ - # Default to Bash >= 4.x - __git_index_file_list_filter_bash -} - -# Execute git ls-files, returning paths relative to the directory -# specified in the first argument, and using the options specified in -# the second argument. +# Execute 'git ls-files', unless the --committable option is specified, in +# which case it runs 'git diff-index' to find out the files that can be +# committed. It return paths relative to the directory specified in the first +# argument, and using the options specified in the second argument. __git_ls_files_helper () { ( test -n "${CDPATH+set}" && unset CDPATH - # NOTE: $2 is not quoted in order to support multiple options - cd "$1" && git ls-files --exclude-standard $2 + cd "$1" + if [ "$2" == "--committable" ]; then + git diff-index --name-only --relative HEAD + else + # NOTE: $2 is not quoted in order to support multiple options + git ls-files --exclude-standard $2 + fi ) 2>/dev/null } -# Execute git diff-index, returning paths relative to the directory -# specified in the first argument, and using the tree object id -# specified in the second argument. -__git_diff_index_helper () -{ - ( - test -n "${CDPATH+set}" && unset CDPATH - cd "$1" && git diff-index --name-only --relative "$2" - ) 2>/dev/null -} - # __git_index_files accepts 1 or 2 arguments: # 1: Options to pass to ls-files (required). -# Supported options are --cached, --modified, --deleted, --others, -# and --directory. # 2: A directory path (optional). # If provided, only files within the specified directory are listed. # Sub directories are never recursed. Path must have a trailing # slash. __git_index_files () { - local dir="$(__gitdir)" root="${2-.}" + local dir="$(__gitdir)" root="${2-.}" file if [ -d "$dir" ]; then - __git_ls_files_helper "$root" "$1" | __git_index_file_list_filter | - sort | uniq - fi -} - -# __git_diff_index_files accepts 1 or 2 arguments: -# 1) The id of a tree object. -# 2) A directory path (optional). -# If provided, only files within the specified directory are listed. -# Sub directories are never recursed. Path must have a trailing -# slash. -__git_diff_index_files () -{ - local dir="$(__gitdir)" root="${2-.}" - - if [ -d "$dir" ]; then - __git_diff_index_helper "$root" "$1" | __git_index_file_list_filter | - sort | uniq + __git_ls_files_helper "$root" "$1" | + while read -r file; do + case "$file" in + ?*/*) echo "${file%%/*}" ;; + *) echo "$file" ;; + esac + done | sort | uniq fi } @@ -427,14 +371,8 @@ __git_refs () done ;; *) - git ls-remote "$dir" HEAD ORIG_HEAD 'refs/tags/*' 'refs/heads/*' 'refs/remotes/*' 2>/dev/null | \ - while read -r hash i; do - case "$i" in - *^{}) ;; - refs/*) echo "${i#refs/*/}" ;; - *) echo "$i" ;; - esac - done + echo "HEAD" + git for-each-ref --format="%(refname:short)" -- "refs/remotes/$dir/" | sed -e "s#^$dir/##" ;; esac } @@ -552,44 +490,23 @@ __git_complete_revlist_file () } -# __git_complete_index_file requires 1 argument: the options to pass to -# ls-file +# __git_complete_index_file requires 1 argument: +# 1: the options to pass to ls-file +# +# The exception is --committable, which finds the files appropriate commit. __git_complete_index_file () { - local pfx cur_="$cur" + local pfx="" cur_="$cur" case "$cur_" in ?*/*) pfx="${cur_%/*}" cur_="${cur_##*/}" pfx="${pfx}/" - - __gitcomp_file "$(__git_index_files "$1" "$pfx")" "$pfx" "$cur_" - ;; - *) - __gitcomp_file "$(__git_index_files "$1")" "" "$cur_" ;; esac -} - -# __git_complete_diff_index_file requires 1 argument: the id of a tree -# object -__git_complete_diff_index_file () -{ - local pfx cur_="$cur" - - case "$cur_" in - ?*/*) - pfx="${cur_%/*}" - cur_="${cur_##*/}" - pfx="${pfx}/" - __gitcomp_file "$(__git_diff_index_files "$1" "$pfx")" "$pfx" "$cur_" - ;; - *) - __gitcomp_file "$(__git_diff_index_files "$1")" "" "$cur_" - ;; - esac + __gitcomp_file "$(__git_index_files "$1" "$pfx")" "$pfx" "$cur_" } __git_complete_file () @@ -1213,7 +1130,7 @@ _git_commit () esac if git rev-parse --verify --quiet HEAD >/dev/null; then - __git_complete_diff_index_file "HEAD" + __git_complete_index_file "--committable" else # This is the first commit __git_complete_index_file "--cached" @@ -1294,7 +1211,7 @@ _git_difftool () return ;; esac - __git_complete_file + __git_complete_revlist_file } __git_fetch_options=" @@ -2360,7 +2277,7 @@ _git_show () return ;; esac - __git_complete_file + __git_complete_revlist_file } _git_show_branch () @@ -2663,7 +2580,7 @@ if [[ -n ${ZSH_VERSION-} ]]; then --*=*|*.) ;; *) c="$c " ;; esac - array[$#array+1]="$c" + array+=("$c") done compset -P '*[=:]' compadd -Q -S '' -p "${2-}" -a -- array && _ret=0 @@ -2689,35 +2606,19 @@ if [[ -n ${ZSH_VERSION-} ]]; then compadd -Q -p "${2-}" -f -- ${=1} && _ret=0 } - __git_zsh_helper () - { - emulate -L ksh - local cur cword prev - cur=${words[CURRENT-1]} - prev=${words[CURRENT-2]} - let cword=CURRENT-1 - __${service}_main - } - _git () { - emulate -L zsh - local _ret=1 - __git_zsh_helper - let _ret && _default -S '' && _ret=0 + local _ret=1 cur cword prev + cur=${words[CURRENT]} + prev=${words[CURRENT-1]} + let cword=CURRENT-1 + emulate ksh -c __${service}_main + let _ret && _default && _ret=0 return _ret } compdef _git git gitk return -elif [[ -n ${BASH_VERSION-} ]]; then - if ((${BASH_VERSINFO[0]} < 4)); then - # compopt is not supported - __git_index_file_list_filter () - { - __git_index_file_list_filter_compat - } - fi fi __git_func_wrap () diff --git a/contrib/completion/git-completion.zsh b/contrib/completion/git-completion.zsh index 2565d2eef4..fac5e711eb 100644 --- a/contrib/completion/git-completion.zsh +++ b/contrib/completion/git-completion.zsh @@ -4,18 +4,17 @@ # # Copyright (c) 2012-2013 Felipe Contreras <felipe.contreras@gmail.com> # -# You need git's bash completion script installed somewhere, by default on the -# same directory as this script. +# You need git's bash completion script installed somewhere, by default it +# would be the location bash-completion uses. # -# If your script is on ~/.git-completion.sh instead, you can configure it on -# your ~/.zshrc: +# If your script is somewhere else, you can configure it on your ~/.zshrc: # # zstyle ':completion:*:*:git:*' script ~/.git-completion.sh # -# The recommended way to install this script is to copy to -# '~/.zsh/completion/_git', and then add the following to your ~/.zshrc file: +# The recommended way to install this script is to copy to '~/.zsh/_git', and +# then add the following to your ~/.zshrc file: # -# fpath=(~/.zsh/completion $fpath) +# fpath=(~/.zsh $fpath) complete () { @@ -27,7 +26,19 @@ zstyle -T ':completion:*:*:git:*' tag-order && \ zstyle ':completion:*:*:git:*' tag-order 'common-commands' zstyle -s ":completion:*:*:git:*" script script -test -z "$script" && script="$(dirname ${funcsourcetrace[1]%:*})"/git-completion.bash +if [ -z "$script" ]; then + local -a locations + local e + locations=( + '/etc/bash_completion.d/git' # fedora, old debian + '/usr/share/bash-completion/completions/git' # arch, ubuntu, new debian + '/usr/share/bash-completion/git' # gentoo + $(dirname ${funcsourcetrace[1]%:*})/git-completion.bash + ) + for e in $locations; do + test -f $e && script="$e" && break + done +fi ZSH_VERSION='' . "$script" __gitcomp () diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh index eaf5c369aa..86a4f3fa49 100644 --- a/contrib/completion/git-prompt.sh +++ b/contrib/completion/git-prompt.sh @@ -20,7 +20,8 @@ # <post>, which are strings you would put in $PS1 before # and after the status string generated by the git-prompt # machinery. e.g. -# PROMPT_COMMAND='__git_ps1 "\u@\h:\w" "\\\$ "' +# Bash: PROMPT_COMMAND='__git_ps1 "\u@\h:\w" "\\\$ "' +# ZSH: precmd () { __git_ps1 "%n" ":%~$ " "|%s" } # will show username, at-sign, host, colon, cwd, then # various status string, followed by dollar and SP, as # your prompt. @@ -124,7 +125,7 @@ __git_ps1_show_upstream () fi ;; svn-remote.*.url) - svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value" + svn_remote[$((${#svn_remote[@]} + 1))]="$value" svn_url_pattern+="\\|$value" upstream=svn+git # default upstream is SVN if available, else git ;; @@ -146,10 +147,11 @@ __git_ps1_show_upstream () svn*) # get the upstream from the "git-svn-id: ..." in a commit message # (git-svn uses essentially the same procedure internally) - local svn_upstream=($(git log --first-parent -1 \ + local -a svn_upstream + svn_upstream=($(git log --first-parent -1 \ --grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null)) if [[ 0 -ne ${#svn_upstream[@]} ]]; then - svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]} + svn_upstream=${svn_upstream[${#svn_upstream[@]} - 2]} svn_upstream=${svn_upstream%@*} local n_stop="${#svn_remote[@]}" for ((n=1; n <= n_stop; n++)); do @@ -222,6 +224,85 @@ __git_ps1_show_upstream () } +# Helper function that is meant to be called from __git_ps1. It +# builds up a gitstring injecting color codes into the appropriate +# places. +__git_ps1_colorize_gitstring () +{ + if [[ -n ${ZSH_VERSION-} ]]; then + local c_red='%F{red}' + local c_green='%F{green}' + local c_lblue='%F{blue}' + local c_clear='%f' + local bad_color=$c_red + local ok_color=$c_green + local branch_color="$c_clear" + local flags_color="$c_lblue" + local branchstring="$c${b##refs/heads/}" + + if [ $detached = no ]; then + branch_color="$ok_color" + else + branch_color="$bad_color" + fi + + gitstring="$branch_color$branchstring$c_clear" + + if [ -n "$w$i$s$u$r$p" ]; then + gitstring="$gitstring$z" + fi + if [ "$w" = "*" ]; then + gitstring="$gitstring$bad_color$w" + fi + if [ -n "$i" ]; then + gitstring="$gitstring$ok_color$i" + fi + if [ -n "$s" ]; then + gitstring="$gitstring$flags_color$s" + fi + if [ -n "$u" ]; then + gitstring="$gitstring$bad_color$u" + fi + gitstring="$gitstring$c_clear$r$p" + return + fi + local c_red='\e[31m' + local c_green='\e[32m' + local c_lblue='\e[1;34m' + local c_clear='\e[0m' + local bad_color=$c_red + local ok_color=$c_green + local branch_color="$c_clear" + local flags_color="$c_lblue" + local branchstring="$c${b##refs/heads/}" + + if [ $detached = no ]; then + branch_color="$ok_color" + else + branch_color="$bad_color" + fi + + # Setting gitstring directly with \[ and \] around colors + # is necessary to prevent wrapping issues! + gitstring="\[$branch_color\]$branchstring\[$c_clear\]" + + if [ -n "$w$i$s$u$r$p" ]; then + gitstring="$gitstring$z" + fi + if [ "$w" = "*" ]; then + gitstring="$gitstring\[$bad_color\]$w" + fi + if [ -n "$i" ]; then + gitstring="$gitstring\[$ok_color\]$i" + fi + if [ -n "$s" ]; then + gitstring="$gitstring\[$flags_color\]$s" + fi + if [ -n "$u" ]; then + gitstring="$gitstring\[$bad_color\]$u" + fi + gitstring="$gitstring\[$c_clear\]$r$p" +} # __git_ps1 accepts 0 or 1 arguments (i.e., format string) # when called from PS1 using command substitution @@ -279,6 +360,7 @@ __git_ps1 () step=$(cat "$g/rebase-apply/next") total=$(cat "$g/rebase-apply/last") if [ -f "$g/rebase-apply/rebasing" ]; then + b="$(cat "$g/rebase-apply/head-name")" r="|REBASE" elif [ -f "$g/rebase-apply/applying" ]; then r="|AM" @@ -295,6 +377,7 @@ __git_ps1 () r="|BISECTING" fi + test -n "$b" || b="$(git symbolic-ref HEAD 2>/dev/null)" || { detached=yes b="$( @@ -359,54 +442,20 @@ __git_ps1 () fi fi + local z="${GIT_PS1_STATESEPARATOR-" "}" local f="$w$i$s$u" if [ $pcmode = yes ]; then local gitstring= if [ -n "${GIT_PS1_SHOWCOLORHINTS-}" ]; then - local c_red='\e[31m' - local c_green='\e[32m' - local c_lblue='\e[1;34m' - local c_clear='\e[0m' - local bad_color=$c_red - local ok_color=$c_green - local branch_color="$c_clear" - local flags_color="$c_lblue" - local branchstring="$c${b##refs/heads/}" - - if [ $detached = no ]; then - branch_color="$ok_color" - else - branch_color="$bad_color" - fi - - # Setting gitstring directly with \[ and \] around colors - # is necessary to prevent wrapping issues! - gitstring="\[$branch_color\]$branchstring\[$c_clear\]" - - if [ -n "$w$i$s$u$r$p" ]; then - gitstring="$gitstring " - fi - if [ "$w" = "*" ]; then - gitstring="$gitstring\[$bad_color\]$w" - fi - if [ -n "$i" ]; then - gitstring="$gitstring\[$ok_color\]$i" - fi - if [ -n "$s" ]; then - gitstring="$gitstring\[$flags_color\]$s" - fi - if [ -n "$u" ]; then - gitstring="$gitstring\[$bad_color\]$u" - fi - gitstring="$gitstring\[$c_clear\]$r$p" + __git_ps1_colorize_gitstring else - gitstring="$c${b##refs/heads/}${f:+ $f}$r$p" + gitstring="$c${b##refs/heads/}${f:+$z$f}$r$p" fi gitstring=$(printf -- "$printf_format" "$gitstring") PS1="$ps1pc_start$gitstring$ps1pc_end" else # NO color option unless in PROMPT_COMMAND mode - printf -- "$printf_format" "$c${b##refs/heads/}${f:+ $f}$r$p" + printf -- "$printf_format" "$c${b##refs/heads/}${f:+$z$f}$r$p" fi fi } diff --git a/contrib/continuous/cidaemon b/contrib/continuous/cidaemon deleted file mode 100644 index 4009a151de..0000000000 --- a/contrib/continuous/cidaemon +++ /dev/null @@ -1,503 +0,0 @@ -#!/usr/bin/perl -# -# A daemon that waits for update events sent by its companion -# post-receive-cinotify hook, checks out a new copy of source, -# compiles it, and emails the guilty parties if the compile -# (and optionally test suite) fails. -# -# To use this daemon, configure it and run it. It will disconnect -# from your terminal and fork into the background. The daemon must -# have local filesystem access to the source repositories, as it -# uses objects/info/alternates to avoid copying objects. -# -# Add its companion post-receive-cinotify hook as the post-receive -# hook to each repository that the daemon should monitor. Yes, a -# single daemon can monitor more than one repository. -# -# To use multiple daemons on the same system, give them each a -# unique queue file and tmpdir. -# -# Global Config -# ------------- -# Reads from a Git style configuration file. This will be -# ~/.gitconfig by default but can be overridden by setting -# the GIT_CONFIG_FILE environment variable before starting. -# -# cidaemon.smtpHost -# Hostname of the SMTP server the daemon will send email -# through. Defaults to 'localhost'. -# -# cidaemon.smtpUser -# Username to authenticate to the SMTP server as. This -# variable is optional; if it is not supplied then no -# authentication will be performed. -# -# cidaemon.smtpPassword -# Password to authenticate to the SMTP server as. This -# variable is optional. If not supplied but smtpUser was, -# the daemon prompts for the password before forking into -# the background. -# -# cidaemon.smtpAuth -# Type of authentication to perform with the SMTP server. -# If set to 'login' and smtpUser was defined, this will -# use the AUTH LOGIN command, which is suitable for use -# with at least one version of Microsoft Exchange Server. -# If not set the daemon will use whatever auth methods -# are supported by your version of Net::SMTP. -# -# cidaemon.email -# Email address that daemon generated emails will be sent -# from. This should be a useful email address within your -# organization. Required. -# -# cidaemon.name -# Human friendly name that the daemon will send emails as. -# Defaults to 'cidaemon'. -# -# cidaemon.scanDelay -# Number of seconds to sleep between polls of the queue file. -# Defaults to 60. -# -# cidaemon.recentCache -# Number of recent commit SHA-1s per repository to cache and -# skip building if they appear again. This is useful to avoid -# rebuilding the same commit multiple times just because it was -# pushed into more than one branch. Defaults to 100. -# -# cidaemon.tmpdir -# Scratch directory to create the builds within. The daemon -# makes a new subdirectory for each build, then deletes it when -# the build has finished. The pid file is also placed here. -# Defaults to '/tmp'. -# -# cidaemon.queue -# Path to the queue file that the post-receive-cinotify hook -# appends events to. This file is polled by the daemon. It -# must not be on an NFS mount (uses flock). Required. -# -# cidaemon.nocc -# Perl regex patterns to match against author and committer -# lines. If a pattern matches, that author or committer will -# not be notified of a build failure. -# -# Per Repository Config -# ---------------------- -# Read from the source repository's config file. -# -# builder.command -# Shell command to execute the build. This command must -# return 0 on "success" and non-zero on failure. If you -# also want to run a test suite, make sure your command -# does that too. Required. -# -# builder.queue -# Queue file to notify the cidaemon through. Should match -# cidaemon.queue. If not set the hook will not notify the -# cidaemon. -# -# builder.skip -# Perl regex patterns of refs that should not be sent to -# cidaemon. Updates of these refs will be ignored. -# -# builder.newBranchBase -# Glob patterns of refs that should be used to form the -# 'old' revions of a newly created ref. This should set -# to be globs that match your 'mainline' branches. This -# way a build failure of a brand new topic branch does not -# attempt to email everyone since the beginning of time; -# instead it only emails those authors of commits not in -# these 'mainline' branches. - -local $ENV{PATH} = join ':', qw( - /opt/git/bin - /usr/bin - /bin - ); - -use strict; -use warnings; -use FindBin qw($RealBin); -use File::Spec; -use lib File::Spec->catfile($RealBin, '..', 'perl5'); -use Storable qw(retrieve nstore); -use Fcntl ':flock'; -use POSIX qw(strftime); -use Getopt::Long qw(:config no_auto_abbrev auto_help); - -sub git_config ($;$) -{ - my $var = shift; - my $required = shift || 0; - local *GIT; - open GIT, '-|','git','config','--get',$var; - my $r = <GIT>; - chop $r if $r; - close GIT; - die "error: $var not set.\n" if ($required && !$r); - return $r; -} - -package EXCHANGE_NET_SMTP; - -# Microsoft Exchange Server requires an 'AUTH LOGIN' -# style of authentication. This is different from -# the default supported by Net::SMTP so we subclass -# and override the auth method to support that. - -use Net::SMTP; -use Net::Cmd; -use MIME::Base64 qw(encode_base64); -our @ISA = qw(Net::SMTP); -our $auth_type = ::git_config 'cidaemon.smtpAuth'; - -sub new -{ - my $self = shift; - my $type = ref($self) || $self; - $type->SUPER::new(@_); -} - -sub auth -{ - my $self = shift; - return $self->SUPER::auth(@_) unless $auth_type eq 'login'; - - my $user = encode_base64 shift, ''; - my $pass = encode_base64 shift, ''; - return 0 unless CMD_MORE == $self->command("AUTH LOGIN")->response; - return 0 unless CMD_MORE == $self->command($user)->response; - CMD_OK == $self->command($pass)->response; -} - -package main; - -my ($debug_flag, %recent); - -my $ex_host = git_config('cidaemon.smtpHost') || 'localhost'; -my $ex_user = git_config('cidaemon.smtpUser'); -my $ex_pass = git_config('cidaemon.smtpPassword'); - -my $ex_from_addr = git_config('cidaemon.email', 1); -my $ex_from_name = git_config('cidaemon.name') || 'cidaemon'; - -my $scan_delay = git_config('cidaemon.scanDelay') || 60; -my $recent_size = git_config('cidaemon.recentCache') || 100; -my $tmpdir = git_config('cidaemon.tmpdir') || '/tmp'; -my $queue_name = git_config('cidaemon.queue', 1); -my $queue_lock = "$queue_name.lock"; - -my @nocc_list; -open GIT,'git config --get-all cidaemon.nocc|'; -while (<GIT>) { - chop; - push @nocc_list, $_; -} -close GIT; - -sub nocc_author ($) -{ - local $_ = shift; - foreach my $pat (@nocc_list) { - return 1 if /$pat/; - } - 0; -} - -sub input_echo ($) -{ - my $prompt = shift; - - local $| = 1; - print $prompt; - my $input = <STDIN>; - chop $input; - return $input; -} - -sub input_noecho ($) -{ - my $prompt = shift; - - my $end = sub {system('stty','echo');print "\n";exit}; - local $SIG{TERM} = $end; - local $SIG{INT} = $end; - system('stty','-echo'); - - local $| = 1; - print $prompt; - my $input = <STDIN>; - system('stty','echo'); - print "\n"; - chop $input; - return $input; -} - -sub rfc2822_date () -{ - strftime("%a, %d %b %Y %H:%M:%S %Z", localtime); -} - -sub send_email ($$$) -{ - my ($subj, $body, $to) = @_; - my $now = rfc2822_date; - my $to_str = ''; - my @rcpt_to; - foreach (@$to) { - my $s = $_; - $s =~ s/^/"/; - $s =~ s/(\s+<)/"$1/; - $to_str .= ', ' if $to_str; - $to_str .= $s; - push @rcpt_to, $1 if $s =~ /<(.*)>/; - } - die "Nobody to send to.\n" unless @rcpt_to; - my $msg = <<EOF; -From: "$ex_from_name" <$ex_from_addr> -To: $to_str -Date: $now -Subject: $subj - -$body -EOF - - my $smtp = EXCHANGE_NET_SMTP->new(Host => $ex_host) - or die "Cannot connect to $ex_host: $!\n"; - if ($ex_user && $ex_pass) { - $smtp->auth($ex_user,$ex_pass) - or die "$ex_host rejected $ex_user\n"; - } - $smtp->mail($ex_from_addr) - or die "$ex_host rejected $ex_from_addr\n"; - scalar($smtp->recipient(@rcpt_to, { SkipBad => 1 })) - or die "$ex_host did not accept any addresses.\n"; - $smtp->data($msg) - or die "$ex_host rejected message data\n"; - $smtp->quit; -} - -sub pop_queue () -{ - open LOCK, ">$queue_lock" or die "Can't open $queue_lock: $!"; - flock LOCK, LOCK_EX; - - my $queue = -f $queue_name ? retrieve $queue_name : []; - my $ent = shift @$queue; - nstore $queue, $queue_name; - - flock LOCK, LOCK_UN; - close LOCK; - $ent; -} - -sub git_exec (@) -{ - system('git',@_) == 0 or die "Cannot git " . join(' ', @_) . "\n"; -} - -sub git_val (@) -{ - open(C, '-|','git',@_); - my $r = <C>; - chop $r if $r; - close C; - $r; -} - -sub do_build ($$) -{ - my ($git_dir, $new) = @_; - - my $tmp = File::Spec->catfile($tmpdir, "builder$$"); - system('rm','-rf',$tmp) == 0 or die "Cannot clear $tmp\n"; - die "Cannot clear $tmp.\n" if -e $tmp; - - my $result = 1; - eval { - my $command; - { - local $ENV{GIT_DIR} = $git_dir; - $command = git_val 'config','builder.command'; - } - die "No builder.command for $git_dir.\n" unless $command; - - git_exec 'clone','-n','-l','-s',$git_dir,$tmp; - chmod 0700, $tmp or die "Cannot lock $tmp\n"; - chdir $tmp or die "Cannot enter $tmp\n"; - - git_exec 'update-ref','HEAD',$new; - git_exec 'read-tree','-m','-u','HEAD','HEAD'; - system $command; - if ($? == -1) { - print STDERR "failed to execute '$command': $!\n"; - $result = 1; - } elsif ($? & 127) { - my $sig = $? & 127; - print STDERR "'$command' died from signal $sig\n"; - $result = 1; - } else { - my $r = $? >> 8; - print STDERR "'$command' exited with $r\n" if $r; - $result = $r; - } - }; - if ($@) { - $result = 2; - print STDERR "$@\n"; - } - - chdir '/'; - system('rm','-rf',$tmp); - rmdir $tmp; - $result; -} - -sub build_failed ($$$$$) -{ - my ($git_dir, $ref, $old, $new, $msg) = @_; - - $git_dir =~ m,/([^/]+)$,; - my $repo_name = $1; - $ref =~ s,^refs/(heads|tags)/,,; - - my %authors; - my $shortlog; - my $revstr; - { - local $ENV{GIT_DIR} = $git_dir; - my @revs = ($new); - push @revs, '--not', @$old if @$old; - open LOG,'-|','git','rev-list','--pretty=raw',@revs; - while (<LOG>) { - if (s/^(author|committer) //) { - chomp; - s/>.*$/>/; - $authors{$_} = 1 unless nocc_author $_; - } - } - close LOG; - open LOG,'-|','git','shortlog',@revs; - $shortlog .= $_ while <LOG>; - close LOG; - $revstr = join(' ', @revs); - } - - my @to = sort keys %authors; - unless (@to) { - print STDERR "error: No authors in $revstr\n"; - return; - } - - my $subject = "[$repo_name] $ref : Build Failed"; - my $body = <<EOF; -Project: $git_dir -Branch: $ref -Commits: $revstr - -$shortlog -Build Output: --------------------------------------------------------------- -$msg -EOF - send_email($subject, $body, \@to); -} - -sub run_build ($$$$) -{ - my ($git_dir, $ref, $old, $new) = @_; - - if ($debug_flag) { - my @revs = ($new); - push @revs, '--not', @$old if @$old; - print "BUILDING $git_dir\n"; - print " BRANCH: $ref\n"; - print " COMMITS: ", join(' ', @revs), "\n"; - } - - local(*R, *W); - pipe R, W or die "cannot pipe builder: $!"; - - my $builder = fork(); - if (!defined $builder) { - die "cannot fork builder: $!"; - } elsif (0 == $builder) { - close R; - close STDIN;open(STDIN, '/dev/null'); - open(STDOUT, '>&W'); - open(STDERR, '>&W'); - exit do_build $git_dir, $new; - } else { - close W; - my $out = ''; - $out .= $_ while <R>; - close R; - waitpid $builder, 0; - build_failed $git_dir, $ref, $old, $new, $out if $?; - } - - print "DONE\n\n" if $debug_flag; -} - -sub daemon_loop () -{ - my $run = 1; - my $stop_sub = sub {$run = 0}; - $SIG{HUP} = $stop_sub; - $SIG{INT} = $stop_sub; - $SIG{TERM} = $stop_sub; - - mkdir $tmpdir, 0755; - my $pidfile = File::Spec->catfile($tmpdir, "cidaemon.pid"); - open(O, ">$pidfile"); print O "$$\n"; close O; - - while ($run) { - my $ent = pop_queue; - if ($ent) { - my ($git_dir, $ref, $old, $new) = @$ent; - - $ent = $recent{$git_dir}; - $recent{$git_dir} = $ent = [[], {}] unless $ent; - my ($rec_arr, $rec_hash) = @$ent; - next if $rec_hash->{$new}++; - while (@$rec_arr >= $recent_size) { - my $to_kill = shift @$rec_arr; - delete $rec_hash->{$to_kill}; - } - push @$rec_arr, $new; - - run_build $git_dir, $ref, $old, $new; - } else { - sleep $scan_delay; - } - } - - unlink $pidfile; -} - -$debug_flag = 0; -GetOptions( - 'debug|d' => \$debug_flag, - 'smtp-user=s' => \$ex_user, -) or die "usage: $0 [--debug] [--smtp-user=user]\n"; - -$ex_pass = input_noecho("$ex_user SMTP password: ") - if ($ex_user && !$ex_pass); - -if ($debug_flag) { - daemon_loop; - exit 0; -} - -my $daemon = fork(); -if (!defined $daemon) { - die "cannot fork daemon: $!"; -} elsif (0 == $daemon) { - close STDIN;open(STDIN, '/dev/null'); - close STDOUT;open(STDOUT, '>/dev/null'); - close STDERR;open(STDERR, '>/dev/null'); - daemon_loop; - exit 0; -} else { - print "Daemon $daemon running in the background.\n"; -} diff --git a/contrib/continuous/post-receive-cinotify b/contrib/continuous/post-receive-cinotify deleted file mode 100644 index b8f5a609af..0000000000 --- a/contrib/continuous/post-receive-cinotify +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/perl -# -# A hook that notifies its companion cidaemon through a simple -# queue file that a ref has been updated via a push (actually -# by a receive-pack running on the server). -# -# See cidaemon for per-repository configuration details. -# -# To use this hook, add it as the post-receive hook, make it -# executable, and set its configuration options. -# - -local $ENV{PATH} = '/opt/git/bin'; - -use strict; -use warnings; -use File::Spec; -use Storable qw(retrieve nstore); -use Fcntl ':flock'; - -my $git_dir = File::Spec->rel2abs($ENV{GIT_DIR}); -my $queue_name = `git config --get builder.queue`;chop $queue_name; -$queue_name =~ m,^([^\s]+)$,; $queue_name = $1; # untaint -unless ($queue_name) { - 1 while <STDIN>; - print STDERR "\nerror: builder.queue not set. Not enqueing.\n\n"; - exit; -} -my $queue_lock = "$queue_name.lock"; - -my @skip; -open S, "git config --get-all builder.skip|"; -while (<S>) { - chop; - push @skip, $_; -} -close S; - -my @new_branch_base; -open S, "git config --get-all builder.newBranchBase|"; -while (<S>) { - chop; - push @new_branch_base, $_; -} -close S; - -sub skip ($) -{ - local $_ = shift; - foreach my $p (@skip) { - return 1 if /^$p/; - } - 0; -} - -open LOCK, ">$queue_lock" or die "Can't open $queue_lock: $!"; -flock LOCK, LOCK_EX; - -my $queue = -f $queue_name ? retrieve $queue_name : []; -my %existing; -foreach my $r (@$queue) { - my ($gd, $ref) = @$r; - $existing{$gd}{$ref} = $r; -} - -my @new_branch_commits; -my $loaded_new_branch_commits = 0; - -while (<STDIN>) { - chop; - my ($old, $new, $ref) = split / /, $_, 3; - - next if $old eq $new; - next if $new =~ /^0{40}$/; - next if skip $ref; - - my $r = $existing{$git_dir}{$ref}; - if ($r) { - $r->[3] = $new; - } else { - if ($old =~ /^0{40}$/) { - if (!$loaded_new_branch_commits && @new_branch_base) { - open M,'-|','git','show-ref',@new_branch_base; - while (<M>) { - ($_) = split / /, $_; - push @new_branch_commits, $_; - } - close M; - $loaded_new_branch_commits = 1; - } - $old = [@new_branch_commits]; - } else { - $old = [$old]; - } - - $r = [$git_dir, $ref, $old, $new]; - $existing{$git_dir}{$ref} = $r; - push @$queue, $r; - } -} -nstore $queue, $queue_name; - -flock LOCK, LOCK_UN; -close LOCK; diff --git a/contrib/credential/osxkeychain/git-credential-osxkeychain.c b/contrib/credential/osxkeychain/git-credential-osxkeychain.c index 3940202b36..bcd3f575a3 100644 --- a/contrib/credential/osxkeychain/git-credential-osxkeychain.c +++ b/contrib/credential/osxkeychain/git-credential-osxkeychain.c @@ -127,10 +127,20 @@ static void read_credential(void) *v++ = '\0'; if (!strcmp(buf, "protocol")) { - if (!strcmp(v, "https")) + if (!strcmp(v, "imap")) + protocol = kSecProtocolTypeIMAP; + else if (!strcmp(v, "imaps")) + protocol = kSecProtocolTypeIMAPS; + else if (!strcmp(v, "ftp")) + protocol = kSecProtocolTypeFTP; + else if (!strcmp(v, "ftps")) + protocol = kSecProtocolTypeFTPS; + else if (!strcmp(v, "https")) protocol = kSecProtocolTypeHTTPS; else if (!strcmp(v, "http")) protocol = kSecProtocolTypeHTTP; + else if (!strcmp(v, "smtp")) + protocol = kSecProtocolTypeSMTP; else /* we don't yet handle other protocols */ exit(0); } diff --git a/contrib/mw-to-git/git-remote-mediawiki.perl b/contrib/mw-to-git/git-remote-mediawiki.perl index 9c14c1f88d..c1a967b3d1 100755 --- a/contrib/mw-to-git/git-remote-mediawiki.perl +++ b/contrib/mw-to-git/git-remote-mediawiki.perl @@ -13,6 +13,7 @@ use strict; use MediaWiki::API; +use Git; use DateTime::Format::ISO8601; # By default, use UTF-8 to communicate with Git and the user @@ -41,6 +42,10 @@ use constant NULL_SHA1 => "0000000000000000000000000000000000000000"; # Used on Git's side to reflect empty edit messages on the wiki use constant EMPTY_MESSAGE => '*Empty MediaWiki Message*'; +if (@ARGV != 2) { + exit_error_usage(); +} + my $remotename = $ARGV[0]; my $url = $ARGV[1]; @@ -156,55 +161,15 @@ while (<STDIN>) { ########################## Functions ############################## -## credential API management (generic functions) - -sub credential_read { - my %credential; - my $reader = shift; - my $op = shift; - while (<$reader>) { - my ($key, $value) = /([^=]*)=(.*)/; - if (not defined $key) { - die "ERROR receiving response from git credential $op:\n$_\n"; - } - $credential{$key} = $value; - } - return %credential; -} - -sub credential_write { - my $credential = shift; - my $writer = shift; - # url overwrites other fields, so it must come first - print $writer "url=$credential->{url}\n" if exists $credential->{url}; - while (my ($key, $value) = each(%$credential) ) { - if (length $value && $key ne 'url') { - print $writer "$key=$value\n"; - } - } -} - -sub credential_run { - my $op = shift; - my $credential = shift; - my $pid = open2(my $reader, my $writer, "git credential $op"); - credential_write($credential, $writer); - print $writer "\n"; - close($writer); - - if ($op eq "fill") { - %$credential = credential_read($reader, $op); - } else { - if (<$reader>) { - die "ERROR while running git credential $op:\n$_"; - } - } - close($reader); - waitpid($pid, 0); - my $child_exit_status = $? >> 8; - if ($child_exit_status != 0) { - die "'git credential $op' failed with code $child_exit_status."; - } +## error handling +sub exit_error_usage { + die "ERROR: git-remote-mediawiki module was not called with a correct number of\n" . + "parameters\n" . + "You may obtain this error because you attempted to run the git-remote-mediawiki\n" . + "module directly.\n" . + "This module can be used the following way:\n" . + "\tgit clone mediawiki://<address of a mediawiki>\n" . + "Then, use git commit, push and pull as with every normal git repository.\n"; } # MediaWiki API instance, created lazily. @@ -217,27 +182,45 @@ sub mw_connect_maybe { $mediawiki = MediaWiki::API->new; $mediawiki->{config}->{api_url} = "$url/api.php"; if ($wiki_login) { - my %credential = (url => $url); - $credential{username} = $wiki_login; - $credential{password} = $wiki_passwd; - credential_run("fill", \%credential); + my %credential = ( + 'url' => $url, + 'username' => $wiki_login, + 'password' => $wiki_passwd + ); + Git::credential(\%credential); my $request = {lgname => $credential{username}, lgpassword => $credential{password}, lgdomain => $wiki_domain}; if ($mediawiki->login($request)) { - credential_run("approve", \%credential); + Git::credential(\%credential, 'approve'); print STDERR "Logged in mediawiki user \"$credential{username}\".\n"; } else { print STDERR "Failed to log in mediawiki user \"$credential{username}\" on $url\n"; print STDERR " (error " . $mediawiki->{error}->{code} . ': ' . $mediawiki->{error}->{details} . ")\n"; - credential_run("reject", \%credential); + Git::credential(\%credential, 'reject'); exit 1; } } } +sub fatal_mw_error { + my $action = shift; + print STDERR "fatal: could not $action.\n"; + print STDERR "fatal: '$url' does not appear to be a mediawiki\n"; + if ($url =~ /^https/) { + print STDERR "fatal: make sure '$url/api.php' is a valid page\n"; + print STDERR "fatal: and the SSL certificate is correct.\n"; + } else { + print STDERR "fatal: make sure '$url/api.php' is a valid page.\n"; + } + print STDERR "fatal: (error " . + $mediawiki->{error}->{code} . ': ' . + $mediawiki->{error}->{details} . ")\n"; + exit 1; +} + ## Functions for listing pages on the remote wiki sub get_mw_tracked_pages { my $pages = shift; @@ -290,10 +273,7 @@ sub get_mw_all_pages { aplimit => 'max' }); if (!defined($mw_pages)) { - print STDERR "fatal: could not get the list of wiki pages.\n"; - print STDERR "fatal: '$url' does not appear to be a mediawiki\n"; - print STDERR "fatal: make sure '$url/api.php' is a valid page.\n"; - exit 1; + fatal_mw_error("get the list of wiki pages"); } foreach my $page (@{$mw_pages}) { $pages->{$page->{title}} = $page; @@ -316,10 +296,7 @@ sub get_mw_first_pages { titles => $titles, }); if (!defined($mw_pages)) { - print STDERR "fatal: could not query the list of wiki pages.\n"; - print STDERR "fatal: '$url' does not appear to be a mediawiki\n"; - print STDERR "fatal: make sure '$url/api.php' is a valid page.\n"; - exit 1; + fatal_mw_error("query the list of wiki pages"); } while (my ($id, $page) = each(%{$mw_pages->{query}->{pages}})) { if ($id < 0) { diff --git a/contrib/patches/docbook-xsl-manpages-charmap.patch b/contrib/patches/docbook-xsl-manpages-charmap.patch deleted file mode 100644 index f2b08b4f4a..0000000000 --- a/contrib/patches/docbook-xsl-manpages-charmap.patch +++ /dev/null @@ -1,21 +0,0 @@ -From: Ismail Dönmez <ismail@pardus.org.tr> - -Trying to build the documentation with docbook-xsl 1.73 may result in -the following error. This patch fixes it. - -$ xmlto -m callouts.xsl man git-add.xml -runtime error: file -file:///usr/share/sgml/docbook/xsl-stylesheets-1.73.0/manpages/other.xsl line -129 element call-template -The called template 'read-character-map' was not found. - ---- docbook-xsl-1.73.0/manpages/docbook.xsl.manpages-charmap 2007-07-23 16:24:23.000000000 +0100 -+++ docbook-xsl-1.73.0/manpages/docbook.xsl 2007-07-23 16:25:16.000000000 +0100 -@@ -37,6 +37,7 @@ - <xsl:include href="lists.xsl"/> - <xsl:include href="endnotes.xsl"/> - <xsl:include href="table.xsl"/> -+ <xsl:include href="../common/charmap.xsl"/> - - <!-- * we rename the following just to avoid using params with "man" --> - <!-- * prefixes in the table.xsl stylesheet (because that stylesheet --> diff --git a/contrib/remote-helpers/git-remote-bzr b/contrib/remote-helpers/git-remote-bzr index 10300c63d1..c3a3cac77b 100755 --- a/contrib/remote-helpers/git-remote-bzr +++ b/contrib/remote-helpers/git-remote-bzr @@ -116,7 +116,10 @@ class Marks: self.last_mark = mark def get_tip(self, branch): - return self.tips.get(branch, None) + try: + return str(self.tips[branch]) + except KeyError: + return None def set_tip(self, branch, tip): self.tips[branch] = tip @@ -278,7 +281,7 @@ def export_branch(repo, name): ref = '%s/heads/%s' % (prefix, name) tip = marks.get_tip(name) - branch = bzrlib.branch.Branch.open(branches[name]) + branch = get_remote_branch(name) repo = branch.repository branch.lock_read() @@ -590,7 +593,7 @@ def parse_commit(parser): if ref.startswith('refs/heads/'): name = ref[len('refs/heads/'):] - branch = bzrlib.branch.Branch.open(branches[name]) + branch = get_remote_branch(name) else: die('unknown ref') @@ -621,7 +624,7 @@ def parse_commit(parser): mark = int(mark_ref[1:]) f = { 'mode' : m, 'mark' : mark } elif parser.check('D'): - t, path = line.split(' ') + t, path = line.split(' ', 1) f = { 'deleted' : True } else: die('Unknown file command: %s' % line) @@ -692,7 +695,7 @@ def do_export(parser): for ref, revid in parsed_refs.iteritems(): if ref.startswith('refs/heads/'): name = ref[len('refs/heads/'):] - branch = bzrlib.branch.Branch.open(branches[name]) + branch = get_remote_branch(name) branch.generate_revision_history(revid, marks.get_tip(name)) if name in peers: @@ -749,7 +752,7 @@ def do_list(parser): master_branch = name print "? refs/heads/%s" % name - branch = bzrlib.branch.Branch.open(branches[master_branch]) + branch = get_remote_branch(master_branch) branch.lock_read() for tag, revid in branch.tags.get_tag_dict().items(): try: @@ -765,30 +768,40 @@ def do_list(parser): print "@refs/heads/%s HEAD" % master_branch print -def get_remote_branch(origin, remote_branch, name): - global dirname, peers +def clone(path, remote_branch): + try: + bdir = bzrlib.bzrdir.BzrDir.create(path) + except bzrlib.errors.AlreadyControlDirError: + bdir = bzrlib.bzrdir.BzrDir.open(path) + repo = bdir.find_repository() + repo.fetch(remote_branch.repository) + return remote_branch.sprout(bdir, repository=repo) + +def get_remote_branch(name): + global dirname, branches + + remote_branch = bzrlib.branch.Branch.open(branches[name]) + if isinstance(remote_branch.user_transport, bzrlib.transport.local.LocalTransport): + return remote_branch branch_path = os.path.join(dirname, 'clone', name) - if os.path.exists(branch_path): + + try: + branch = bzrlib.branch.Branch.open(branch_path) + except bzrlib.errors.NotBranchError: + # clone + branch = clone(branch_path, remote_branch) + else: # pull - d = bzrlib.bzrdir.BzrDir.open(branch_path) - branch = d.open_branch() try: - branch.pull(remote_branch, [], None, False) + branch.pull(remote_branch, overwrite=True) except bzrlib.errors.DivergedBranches: # use remote branch for now return remote_branch - else: - # clone - d = origin.sprout(branch_path, None, - hardlink=True, create_tree_if_local=False, - force_new_repo=False, - source_branch=remote_branch) - branch = d.open_branch() return branch -def find_branches(repo, wanted): +def find_branches(repo): transport = repo.bzrdir.root_transport for fn in transport.iter_files_recursive(): @@ -799,16 +812,13 @@ def find_branches(repo, wanted): name = name if name != '' else 'master' name = name.replace('/', '+') - if wanted and not name in wanted: - continue - try: cur = transport.clone(subdir) branch = bzrlib.branch.Branch.open_from_transport(cur) except bzrlib.errors.NotBranchError: continue else: - yield name, branch + yield name, branch.base def get_repo(url, alias): global dirname, peer, branches @@ -841,44 +851,35 @@ def get_repo(url, alias): except bzrlib.errors.NoRepositoryPresent: pass - try: - repo = origin.open_repository() - if not repo.user_transport.listable(): - # this repository is not usable for us - raise bzrlib.errors.NoRepositoryPresent(repo.bzrdir) - except bzrlib.errors.NoRepositoryPresent: - # branch - - name = 'master' - remote_branch = origin.open_branch() - - if not is_local: - peers[name] = remote_branch.base - branch = get_remote_branch(origin, remote_branch, name) - else: - branch = remote_branch - - branches[name] = branch.base + wanted = get_config('remote-bzr.branches').rstrip().split(', ') + # stupid python + wanted = [e for e in wanted if e] - return branch.repository + if not wanted: + try: + repo = origin.open_repository() + if not repo.user_transport.listable(): + # this repository is not usable for us + raise bzrlib.errors.NoRepositoryPresent(repo.bzrdir) + except bzrlib.errors.NoRepositoryPresent: + wanted = ['master'] + + if wanted: + def list_wanted(url, wanted): + for name in wanted: + subdir = name if name != 'master' else '' + yield name, bzrlib.urlutils.join(url, subdir) + + branch_list = list_wanted(url, wanted) else: - # repository - - wanted = get_config('remote-bzr.branches').rstrip().split(', ') - # stupid python - wanted = [e for e in wanted if e] - - for name, remote_branch in find_branches(repo, wanted): + branch_list = find_branches(repo) - if not is_local: - peers[name] = remote_branch.base - branch = get_remote_branch(origin, remote_branch, name) - else: - branch = remote_branch - - branches[name] = branch.base + for name, url in branch_list: + if not is_local: + peers[name] = url + branches[name] = url - return repo + return origin def fix_path(alias, orig_url): url = urlparse.urlparse(orig_url, 'file') diff --git a/contrib/remote-helpers/git-remote-hg b/contrib/remote-helpers/git-remote-hg index 1dd3d7030e..0194c67fb1 100755 --- a/contrib/remote-helpers/git-remote-hg +++ b/contrib/remote-helpers/git-remote-hg @@ -12,7 +12,7 @@ # For remote repositories a local clone is stored in # "$GIT_DIR/hg/origin/clone/.hg/". -from mercurial import hg, ui, bookmarks, context, encoding, node, error, extensions +from mercurial import hg, ui, bookmarks, context, encoding, node, error, extensions, discovery, util import re import sys @@ -29,9 +29,6 @@ import urlparse, hashlib # named branches: # git config --global remote-hg.track-branches false # -# If you don't want to force pushes (and thus risk creating new remote heads): -# git config --global remote-hg.force-push false -# # If you want the equivalent of hg's clone/pull--insecure option: # git config --global remote-hg.insecure true # @@ -55,6 +52,8 @@ EMAIL_RE = re.compile('^([^<>]+[^ \\\t<>])?\\b(?:[ \\t<>]*?)\\b([^ \\t<>]+@[^ \\ AUTHOR_HG_RE = re.compile('^(.*?) ?<(.*?)(?:>(.+)?)?$') RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)') +VERSION = 2 + def die(msg, *args): sys.stderr.write('ERROR: %s\n' % (msg % args)) sys.exit(1) @@ -72,8 +71,11 @@ def hgmode(mode): m = { '100755': 'x', '120000': 'l' } return m.get(mode, '') -def hghex(node): - return hg.node.hex(node) +def hghex(n): + return node.hex(n) + +def hgbin(n): + return node.bin(n) def hgref(ref): return ref.replace('___', ' ') @@ -81,6 +83,11 @@ def hgref(ref): def gitref(ref): return ref.replace(' ', '___') +def check_version(*check): + if not hg_version: + return True + return hg_version >= check + def get_config(config): cmd = ['git', 'config', '--get', config] process = subprocess.Popen(cmd, stdout=subprocess.PIPE) @@ -98,14 +105,27 @@ def get_config_bool(config, default=False): class Marks: - def __init__(self, path): + def __init__(self, path, repo): self.path = path + self.repo = repo + self.clear() + self.load() + + if self.version < VERSION: + if self.version == 1: + self.upgrade_one() + + # upgraded? + if self.version < VERSION: + self.clear() + self.version = VERSION + + def clear(self): self.tips = {} self.marks = {} self.rev_marks = {} self.last_mark = 0 - - self.load() + self.version = 0 def load(self): if not os.path.exists(self.path): @@ -116,12 +136,21 @@ class Marks: self.tips = tmp['tips'] self.marks = tmp['marks'] self.last_mark = tmp['last-mark'] + self.version = tmp.get('version', 1) for rev, mark in self.marks.iteritems(): - self.rev_marks[mark] = int(rev) + self.rev_marks[mark] = rev + + def upgrade_one(self): + def get_id(rev): + return hghex(self.repo.changelog.node(int(rev))) + self.tips = dict((name, get_id(rev)) for name, rev in self.tips.iteritems()) + self.marks = dict((get_id(rev), mark) for rev, mark in self.marks.iteritems()) + self.rev_marks = dict((mark, get_id(rev)) for mark, rev in self.rev_marks.iteritems()) + self.version = 2 def dict(self): - return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark } + return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark, 'version' : self.version } def store(self): json.dump(self.dict(), open(self.path, 'w')) @@ -130,10 +159,10 @@ class Marks: return str(self.dict()) def from_rev(self, rev): - return self.marks[str(rev)] + return self.marks[rev] def to_rev(self, mark): - return self.rev_marks[mark] + return str(self.rev_marks[mark]) def next_mark(self): self.last_mark += 1 @@ -141,19 +170,19 @@ class Marks: def get_mark(self, rev): self.last_mark += 1 - self.marks[str(rev)] = self.last_mark + self.marks[rev] = self.last_mark return self.last_mark def new_mark(self, rev, mark): - self.marks[str(rev)] = mark + self.marks[rev] = mark self.rev_marks[mark] = rev self.last_mark = mark def is_marked(self, rev): - return str(rev) in self.marks + return rev in self.marks def get_tip(self, branch): - return self.tips.get(branch, 0) + return str(self.tips[branch]) def set_tip(self, branch, tip): self.tips[branch] = tip @@ -261,7 +290,7 @@ def get_filechanges(repo, ctx, parent): removed = set() # load earliest manifest first for caching reasons - prev = repo[parent].manifest().copy() + prev = parent.manifest().copy() cur = ctx.manifest() for fn in cur: @@ -329,6 +358,21 @@ def fixup_user(user): return '%s <%s>' % (name, mail) +def updatebookmarks(repo, peer): + remotemarks = peer.listkeys('bookmarks') + localmarks = repo._bookmarks + + if not remotemarks: + return + + for k, v in remotemarks.iteritems(): + localmarks[k] = hgbin(v) + + if hasattr(localmarks, 'write'): + localmarks.write() + else: + bookmarks.write(repo) + def get_repo(url, alias): global dirname, peer @@ -339,35 +383,41 @@ def get_repo(url, alias): if get_config_bool('remote-hg.insecure'): myui.setconfig('web', 'cacerts', '') - try: - mod = extensions.load(myui, 'hgext.schemes', None) - mod.extsetup(myui) - except ImportError: - pass + extensions.loadall(myui) - if hg.islocal(url): + if hg.islocal(url) and not os.environ.get('GIT_REMOTE_HG_TEST_REMOTE'): repo = hg.repository(myui, url) + if not os.path.exists(dirname): + os.makedirs(dirname) else: - local_path = os.path.join(dirname, 'clone') - if not os.path.exists(local_path): - try: - peer, dstpeer = hg.clone(myui, {}, url, local_path, update=True, pull=True) - except: - die('Repository error') - repo = dstpeer.local() - else: - repo = hg.repository(myui, local_path) + shared_path = os.path.join(gitdir, 'hg') + if not os.path.exists(shared_path): try: - peer = hg.peer(myui, {}, url) + hg.clone(myui, {}, url, shared_path, update=False, pull=True) except: die('Repository error') - repo.pull(peer, heads=None, force=True) + + if not os.path.exists(dirname): + os.makedirs(dirname) + + local_path = os.path.join(dirname, 'clone') + if not os.path.exists(local_path): + hg.share(myui, shared_path, local_path, update=False) + + repo = hg.repository(myui, local_path) + try: + peer = hg.peer(myui, {}, url) + except: + die('Repository error') + repo.pull(peer, heads=None, force=True) + + updatebookmarks(repo, peer) return repo def rev_to_mark(rev): global marks - return marks.from_rev(rev) + return marks.from_rev(rev.hex()) def mark_to_rev(mark): global marks @@ -377,17 +427,24 @@ def export_ref(repo, name, kind, head): global prefix, marks, mode ename = '%s/%s' % (kind, name) - tip = marks.get_tip(ename) + try: + tip = marks.get_tip(ename) + tip = repo[tip].rev() + except: + tip = 0 revs = xrange(tip, head.rev() + 1) - count = 0 - - revs = [rev for rev in revs if not marks.is_marked(rev)] + total = len(revs) for rev in revs: c = repo[rev] - (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(c.node()) + node = c.node() + + if marks.is_marked(c.hex()): + continue + + (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(node) rev_branch = extra['branch'] author = "%s %d %s" % (fixup_user(user), time, gittz(tz)) @@ -397,7 +454,7 @@ def export_ref(repo, name, kind, head): else: committer = author - parents = [p for p in repo.changelog.parentrevs(rev) if p >= 0] + parents = [repo[p] for p in repo.changelog.parentrevs(rev) if p >= 0] if len(parents) == 0: modified = c.manifest().keys() @@ -439,7 +496,7 @@ def export_ref(repo, name, kind, head): modified_final = export_files(c.filectx(f) for f in modified) print "commit %s/%s" % (prefix, ename) - print "mark :%d" % (marks.get_mark(rev)) + print "mark :%d" % (marks.get_mark(c.hex())) print "author %s" % (author) print "committer %s" % (committer) print "data %d" % (len(desc)) @@ -450,22 +507,22 @@ def export_ref(repo, name, kind, head): if len(parents) > 1: print "merge :%s" % (rev_to_mark(parents[1])) - for f in modified_final: - print "M %s :%u %s" % f for f in removed: print "D %s" % (fix_file_path(f)) + for f in modified_final: + print "M %s :%u %s" % f print - count += 1 - if (count % 100 == 0): - print "progress revision %d '%s' (%d/%d)" % (rev, name, count, len(revs)) + progress = (rev - tip) + if (progress % 100 == 0): + print "progress revision %d '%s' (%d/%d)" % (rev, name, progress, total) # make sure the ref is updated print "reset %s/%s" % (prefix, ename) - print "from :%u" % rev_to_mark(rev) + print "from :%u" % rev_to_mark(head) print - marks.set_tip(ename, rev) + marks.set_tip(ename, head.hex()) def export_tag(repo, tag): export_ref(repo, tag, 'tags', repo[hgref(tag)]) @@ -497,15 +554,12 @@ def do_capabilities(parser): if os.path.exists(path): print "*import-marks %s" % path print "*export-marks %s" % path + print "option" print -def branch_tip(repo, branch): - # older versions of mercurial don't have this - if hasattr(repo, 'branchtip'): - return repo.branchtip(branch) - else: - return repo.branchtags()[branch] +def branch_tip(branch): + return branches[branch][-1] def get_branch_tip(repo, branch): global branches @@ -517,27 +571,21 @@ def get_branch_tip(repo, branch): # verify there's only one head if (len(heads) > 1): warn("Branch '%s' has more than one head, consider merging" % branch) - return branch_tip(repo, hgref(branch)) + return branch_tip(hgref(branch)) return heads[0] def list_head(repo, cur): - global g_head, bmarks + global g_head, bmarks, fake_bmark - head = bookmarks.readcurrent(repo) - if head: - node = repo[head] - else: - # fake bookmark from current branch - head = cur - node = repo['.'] - if not node: - node = repo['tip'] - if not node: - return - if head == 'default': - head = 'master' - bmarks[head] = node + if 'default' not in branches: + # empty repo + return + + node = repo[branch_tip('default')] + head = 'master' if not 'master' in bmarks else 'default' + fake_bmark = head + bmarks[head] = node head = gitref(head) print "@refs/heads/%s HEAD" % head @@ -551,15 +599,17 @@ def do_list(parser): bmarks[bmark] = repo[node] cur = repo.dirstate.branch() + orig = peer if peer else repo + + for branch, heads in orig.branchmap().iteritems(): + # only open heads + heads = [h for h in heads if 'close' not in repo.changelog.read(h)[5]] + if heads: + branches[branch] = heads list_head(repo, cur) if track_branches: - for branch in repo.branchmap(): - heads = repo.branchheads(branch) - if len(heads): - branches[branch] = heads - for branch in branches: print "? refs/heads/branches/%s" % gitref(branch) @@ -582,6 +632,7 @@ def do_import(parser): if os.path.exists(path): print "feature import-marks=%s" % path print "feature export-marks=%s" % path + print "feature force" sys.stdout.flush() tmp = encoding.encoding @@ -671,6 +722,11 @@ def parse_commit(parser): die('Unknown file command: %s' % line) files[path] = f + # only export the commits if we are on an internal proxy repo + if dry_run and not peer: + parsed_refs[ref] = None + return + def getfilectx(repo, memctx, f): of = files[f] if 'deleted' in of: @@ -692,14 +748,14 @@ def parse_commit(parser): extra['committer'] = "%s %u %u" % committer if from_mark: - p1 = repo.changelog.node(mark_to_rev(from_mark)) + p1 = mark_to_rev(from_mark) else: - p1 = '\0' * 20 + p1 = '0' * 40 if merge_mark: - p2 = repo.changelog.node(mark_to_rev(merge_mark)) + p2 = mark_to_rev(merge_mark) else: - p2 = '\0' * 20 + p2 = '0' * 40 # # If files changed from any of the parents, hg wants to know, but in git if @@ -735,14 +791,12 @@ def parse_commit(parser): tmp = encoding.encoding encoding.encoding = 'utf-8' - node = repo.commitctx(ctx) + node = hghex(repo.commitctx(ctx)) encoding.encoding = tmp - rev = repo[node].rev() - parsed_refs[ref] = node - marks.new_mark(rev, commit_mark) + marks.new_mark(node, commit_mark) def parse_reset(parser): global parsed_refs @@ -758,8 +812,11 @@ def parse_reset(parser): from_mark = parser.get_mark() parser.next() - node = parser.repo.changelog.node(mark_to_rev(from_mark)) - parsed_refs[ref] = node + try: + rev = mark_to_rev(from_mark) + except KeyError: + rev = None + parsed_refs[ref] = rev def parse_tag(parser): name = parser[1] @@ -775,7 +832,7 @@ def parse_tag(parser): def write_tag(repo, tag, node, msg, author): branch = repo[node].branch() - tip = branch_tip(repo, branch) + tip = branch_tip(branch) tip = repo[tip] def getfilectx(repo, memctx, f): @@ -784,18 +841,28 @@ def write_tag(repo, tag, node, msg, author): data = fctx.data() except error.ManifestLookupError: data = "" - content = data + "%s %s\n" % (hghex(node), tag) + content = data + "%s %s\n" % (node, tag) return context.memfilectx(f, content, False, False, None) p1 = tip.hex() - p2 = '\0' * 20 - if not author: - author = (None, 0, 0) - user, date, tz = author + p2 = '0' * 40 + if author: + user, date, tz = author + date_tz = (date, tz) + else: + cmd = ['git', 'var', 'GIT_COMMITTER_IDENT'] + process = subprocess.Popen(cmd, stdout=subprocess.PIPE) + output, _ = process.communicate() + m = re.match('^.* <.*>', output) + if m: + user = m.group(0) + else: + user = repo.ui.username() + date_tz = None ctx = context.memctx(repo, (p1, p2), msg, ['.hgtags'], getfilectx, - user, (date, tz), {'branch' : branch}) + user, date_tz, {'branch' : branch}) tmp = encoding.encoding encoding.encoding = 'utf-8' @@ -804,12 +871,132 @@ def write_tag(repo, tag, node, msg, author): encoding.encoding = tmp - return tagnode + return (tagnode, branch) + +def checkheads_bmark(repo, ref, ctx): + bmark = ref[len('refs/heads/'):] + if not bmark in bmarks: + # new bmark + return True + + ctx_old = bmarks[bmark] + ctx_new = ctx + if not repo.changelog.descendant(ctx_old.rev(), ctx_new.rev()): + if force_push: + print "ok %s forced update" % ref + else: + print "error %s non-fast forward" % ref + return False + + return True + +def checkheads(repo, remote, p_revs): + + remotemap = remote.branchmap() + if not remotemap: + # empty repo + return True + + new = {} + ret = True + + for node, ref in p_revs.iteritems(): + ctx = repo[node] + branch = ctx.branch() + if not branch in remotemap: + # new branch + continue + if not ref.startswith('refs/heads/branches'): + if ref.startswith('refs/heads/'): + if not checkheads_bmark(repo, ref, ctx): + ret = False + + # only check branches + continue + new.setdefault(branch, []).append(ctx.rev()) + + for branch, heads in new.iteritems(): + old = [repo.changelog.rev(x) for x in remotemap[branch]] + for rev in heads: + if check_version(2, 3): + ancestors = repo.changelog.ancestors([rev], stoprev=min(old)) + else: + ancestors = repo.changelog.ancestors(rev) + found = False + + for x in old: + if x in ancestors: + found = True + break + + if found: + continue + + node = repo.changelog.node(rev) + ref = p_revs[node] + if force_push: + print "ok %s forced update" % ref + else: + print "error %s non-fast forward" % ref + ret = False + + return ret + +def push_unsafe(repo, remote, parsed_refs, p_revs): + + force = force_push + + fci = discovery.findcommonincoming + commoninc = fci(repo, remote, force=force) + common, _, remoteheads = commoninc + + if not checkheads(repo, remote, p_revs): + return None + + cg = repo.getbundle('push', heads=list(p_revs), common=common) + + unbundle = remote.capable('unbundle') + if unbundle: + if force: + remoteheads = ['force'] + return remote.unbundle(cg, remoteheads, 'push') + else: + return remote.addchangegroup(cg, 'push', repo.url()) + +def push(repo, remote, parsed_refs, p_revs): + if hasattr(remote, 'canpush') and not remote.canpush(): + print "error cannot push" + + if not p_revs: + # nothing to push + return + + lock = None + unbundle = remote.capable('unbundle') + if not unbundle: + lock = remote.lock() + try: + ret = push_unsafe(repo, remote, parsed_refs, p_revs) + finally: + if lock is not None: + lock.release() + + return ret + +def check_tip(ref, kind, name, heads): + try: + ename = '%s/%s' % (kind, name) + tip = marks.get_tip(ename) + except KeyError: + return True + else: + return tip in heads def do_export(parser): global parsed_refs, bmarks, peer p_bmarks = [] + p_revs = {} parser.next() @@ -827,72 +1014,114 @@ def do_export(parser): else: die('unhandled export command: %s' % line) + need_fetch = False + for ref, node in parsed_refs.iteritems(): + bnode = hgbin(node) if node else None if ref.startswith('refs/heads/branches'): branch = ref[len('refs/heads/branches/'):] - if branch in branches and node in branches[branch]: + if branch in branches and bnode in branches[branch]: # up to date continue + + if peer: + remotemap = peer.branchmap() + if remotemap and branch in remotemap: + heads = [hghex(e) for e in remotemap[branch]] + if not check_tip(ref, 'branches', branch, heads): + print "error %s fetch first" % ref + need_fetch = True + continue + + p_revs[bnode] = ref print "ok %s" % ref elif ref.startswith('refs/heads/'): bmark = ref[len('refs/heads/'):] - p_bmarks.append((bmark, node)) - continue + new = node + old = bmarks[bmark].hex() if bmark in bmarks else '' + + if old == new: + continue + + print "ok %s" % ref + if bmark != fake_bmark and \ + not (bmark == 'master' and bmark not in parser.repo._bookmarks): + p_bmarks.append((ref, bmark, old, new)) + + if peer: + remote_old = peer.listkeys('bookmarks').get(bmark) + if remote_old: + if not check_tip(ref, 'bookmarks', bmark, remote_old): + print "error %s fetch first" % ref + need_fetch = True + continue + + p_revs[bnode] = ref elif ref.startswith('refs/tags/'): + if dry_run: + print "ok %s" % ref + continue tag = ref[len('refs/tags/'):] tag = hgref(tag) author, msg = parsed_tags.get(tag, (None, None)) if mode == 'git': if not msg: - msg = 'Added tag %s for changeset %s' % (tag, hghex(node[:6])); - write_tag(parser.repo, tag, node, msg, author) + msg = 'Added tag %s for changeset %s' % (tag, node[:12]); + tagnode, branch = write_tag(parser.repo, tag, node, msg, author) + p_revs[tagnode] = 'refs/heads/branches/' + gitref(branch) else: fp = parser.repo.opener('localtags', 'a') - fp.write('%s %s\n' % (hghex(node), tag)) + fp.write('%s %s\n' % (node, tag)) fp.close() + p_revs[bnode] = ref print "ok %s" % ref else: # transport-helper/fast-export bugs continue - if peer: - parser.repo.push(peer, force=force_push, newbranch=True) - - # handle bookmarks - for bmark, node in p_bmarks: - ref = 'refs/heads/' + bmark - new = hghex(node) - - if bmark in bmarks: - old = bmarks[bmark].hex() - else: - old = '' + if need_fetch: + print + return - if old == new: - continue + if dry_run: + if peer and not force_push: + checkheads(parser.repo, peer, p_revs) + print + return - if bmark == 'master' and 'master' not in parser.repo._bookmarks: - # fake bookmark - print "ok %s" % ref - continue - elif bookmarks.pushbookmark(parser.repo, bmark, old, new): - # updated locally - pass - else: - print "error %s" % ref - continue + if peer: + if not push(parser.repo, peer, parsed_refs, p_revs): + # do not update bookmarks + print + return - if peer: - rb = peer.listkeys('bookmarks') - old = rb.get(bmark, '') + # update remote bookmarks + remote_bmarks = peer.listkeys('bookmarks') + for ref, bmark, old, new in p_bmarks: + if force_push: + old = remote_bmarks.get(bmark, '') if not peer.pushkey('bookmarks', bmark, old, new): print "error %s" % ref - continue - - print "ok %s" % ref + else: + # update local bookmarks + for ref, bmark, old, new in p_bmarks: + if not bookmarks.pushbookmark(parser.repo, bmark, old, new): + print "error %s" % ref print +def do_option(parser): + global dry_run, force_push + _, key, value = parser.line.split(' ') + if key == 'dry-run': + dry_run = (value == 'true') + print 'ok' + elif key == 'force': + force_push = (value == 'true') + print 'ok' + else: + print 'unsupported' + def fix_path(alias, repo, orig_url): url = urlparse.urlparse(orig_url, 'file') if url.scheme != 'file' or os.path.isabs(url.path): @@ -902,12 +1131,14 @@ def fix_path(alias, repo, orig_url): subprocess.call(cmd) def main(args): - global prefix, dirname, branches, bmarks + global prefix, gitdir, dirname, branches, bmarks global marks, blob_marks, parsed_refs global peer, mode, bad_mail, bad_name global track_branches, force_push, is_tmp global parsed_tags global filenodes + global fake_bmark, hg_version + global dry_run alias = args[1] url = args[2] @@ -915,7 +1146,7 @@ def main(args): hg_git_compat = get_config_bool('remote-hg.hg-git-compat') track_branches = get_config_bool('remote-hg.track-branches', True) - force_push = get_config_bool('remote-hg.force-push') + force_push = False if hg_git_compat: mode = 'hg' @@ -941,6 +1172,12 @@ def main(args): marks = None parsed_tags = {} filenodes = {} + fake_bmark = None + try: + hg_version = tuple(int(e) for e in util.version().split('.')) + except: + hg_version = None + dry_run = False repo = get_repo(url, alias) prefix = 'refs/hg/%s' % alias @@ -948,11 +1185,8 @@ def main(args): if not is_tmp: fix_path(alias, peer or repo, url) - if not os.path.exists(dirname): - os.makedirs(dirname) - marks_path = os.path.join(dirname, 'marks-hg') - marks = Marks(marks_path) + marks = Marks(marks_path, repo) if sys.platform == 'win32': import msvcrt @@ -968,6 +1202,8 @@ def main(args): do_import(parser) elif parser.check('export'): do_export(parser) + elif parser.check('option'): + do_option(parser) else: die('unhandled command: %s' % line) sys.stdout.flush() diff --git a/contrib/remote-helpers/test-bzr.sh b/contrib/remote-helpers/test-bzr.sh index 5dfa070b64..dce281f911 100755 --- a/contrib/remote-helpers/test-bzr.sh +++ b/contrib/remote-helpers/test-bzr.sh @@ -12,86 +12,90 @@ if ! test_have_prereq PYTHON; then test_done fi -if ! "$PYTHON_PATH" -c 'import bzrlib'; then +if ! python -c 'import bzrlib'; then skip_all='skipping remote-bzr tests; bzr not available' test_done fi check () { - (cd $1 && - git log --format='%s' -1 && - git symbolic-ref HEAD) > actual && - (echo $2 && - echo "refs/heads/$3") > expected && + echo $3 > expected && + git --git-dir=$1/.git log --format='%s' -1 $2 > actual test_cmp expected actual } bzr whoami "A U Thor <author@example.com>" test_expect_success 'cloning' ' - (bzr init bzrrepo && - cd bzrrepo && - echo one > content && - bzr add content && - bzr commit -m one - ) && - - git clone "bzr::$PWD/bzrrepo" gitrepo && - check gitrepo one master + ( + bzr init bzrrepo && + cd bzrrepo && + echo one > content && + bzr add content && + bzr commit -m one + ) && + + git clone "bzr::bzrrepo" gitrepo && + check gitrepo HEAD one ' test_expect_success 'pulling' ' - (cd bzrrepo && - echo two > content && - bzr commit -m two - ) && + ( + cd bzrrepo && + echo two > content && + bzr commit -m two + ) && - (cd gitrepo && git pull) && + (cd gitrepo && git pull) && - check gitrepo two master + check gitrepo HEAD two ' test_expect_success 'pushing' ' - (cd gitrepo && - echo three > content && - git commit -a -m three && - git push - ) && - - echo three > expected && - cat bzrrepo/content > actual && - test_cmp expected actual + ( + cd gitrepo && + echo three > content && + git commit -a -m three && + git push + ) && + + echo three > expected && + cat bzrrepo/content > actual && + test_cmp expected actual ' test_expect_success 'roundtrip' ' - (cd gitrepo && - git pull && - git log --format="%s" -1 origin/master > actual) && - echo three > expected && - test_cmp expected actual && + ( + cd gitrepo && + git pull && + git log --format="%s" -1 origin/master > actual + ) && + echo three > expected && + test_cmp expected actual && - (cd gitrepo && git push && git pull) && + (cd gitrepo && git push && git pull) && - (cd bzrrepo && - echo four > content && - bzr commit -m four - ) && + ( + cd bzrrepo && + echo four > content && + bzr commit -m four + ) && - (cd gitrepo && git pull && git push) && + (cd gitrepo && git pull && git push) && - check gitrepo four master && + check gitrepo HEAD four && - (cd gitrepo && - echo five > content && - git commit -a -m five && - git push && git pull - ) && + ( + cd gitrepo && + echo five > content && + git commit -a -m five && + git push && git pull + ) && - (cd bzrrepo && bzr revert) && + (cd bzrrepo && bzr revert) && - echo five > expected && - cat bzrrepo/content > actual && - test_cmp expected actual + echo five > expected && + cat bzrrepo/content > actual && + test_cmp expected actual ' cat > expected <<EOF @@ -101,29 +105,35 @@ cat > expected <<EOF EOF test_expect_success 'special modes' ' - (cd bzrrepo && - echo exec > executable - chmod +x executable && - bzr add executable - bzr commit -m exec && - ln -s content link - bzr add link - bzr commit -m link && - mkdir dir && - bzr add dir && - bzr commit -m dir) && - - (cd gitrepo && - git pull - git ls-tree HEAD > ../actual) && - - test_cmp expected actual && - - (cd gitrepo && - git cat-file -p HEAD:link > ../actual) && - - printf content > expected && - test_cmp expected actual + ( + cd bzrrepo && + echo exec > executable + chmod +x executable && + bzr add executable + bzr commit -m exec && + ln -s content link + bzr add link + bzr commit -m link && + mkdir dir && + bzr add dir && + bzr commit -m dir + ) && + + ( + cd gitrepo && + git pull + git ls-tree HEAD > ../actual + ) && + + test_cmp expected actual && + + ( + cd gitrepo && + git cat-file -p HEAD:link > ../actual + ) && + + printf content > expected && + test_cmp expected actual ' cat > expected <<EOF @@ -134,134 +144,145 @@ cat > expected <<EOF EOF test_expect_success 'moving directory' ' - (cd bzrrepo && - mkdir movedir && - echo one > movedir/one && - echo two > movedir/two && - bzr add movedir && - bzr commit -m movedir && - bzr mv movedir movedir-new && - bzr commit -m movedir-new) && - - (cd gitrepo && - git pull && - git ls-tree HEAD > ../actual) && - - test_cmp expected actual + ( + cd bzrrepo && + mkdir movedir && + echo one > movedir/one && + echo two > movedir/two && + bzr add movedir && + bzr commit -m movedir && + bzr mv movedir movedir-new && + bzr commit -m movedir-new + ) && + + ( + cd gitrepo && + git pull && + git ls-tree HEAD > ../actual + ) && + + test_cmp expected actual ' test_expect_success 'different authors' ' - (cd bzrrepo && - echo john >> content && - bzr commit -m john \ - --author "Jane Rey <jrey@example.com>" \ - --author "John Doe <jdoe@example.com>") && - - (cd gitrepo && - git pull && - git show --format="%an <%ae>, %cn <%ce>" --quiet > ../actual) && - - echo "Jane Rey <jrey@example.com>, A U Thor <author@example.com>" > expected && - test_cmp expected actual + ( + cd bzrrepo && + echo john >> content && + bzr commit -m john \ + --author "Jane Rey <jrey@example.com>" \ + --author "John Doe <jdoe@example.com>" + ) && + + ( + cd gitrepo && + git pull && + git show --format="%an <%ae>, %cn <%ce>" --quiet > ../actual + ) && + + echo "Jane Rey <jrey@example.com>, A U Thor <author@example.com>" > expected && + test_cmp expected actual ' +# cleanup previous stuff +rm -rf bzrrepo gitrepo + test_expect_success 'fetch utf-8 filenames' ' - mkdir -p tmp && cd tmp && - test_when_finished "cd .. && rm -rf tmp && LC_ALL=C" && - - LC_ALL=en_US.UTF-8 - export LC_ALL - ( - bzr init bzrrepo && - cd bzrrepo && - - echo test >> "ærø" && - bzr add "ærø" && - echo test >> "ø~?" && - bzr add "ø~?" && - bzr commit -m add-utf-8 && - echo test >> "ærø" && - bzr commit -m test-utf-8 && - bzr rm "ø~?" && - bzr mv "ærø" "ø~?" && - bzr commit -m bzr-mv-utf-8 - ) && - - ( - git clone "bzr::$PWD/bzrrepo" gitrepo && - cd gitrepo && - git -c core.quotepath=false ls-files > ../actual - ) && - echo "ø~?" > expected && - test_cmp expected actual + test_when_finished "rm -rf bzrrepo gitrepo && LC_ALL=C" && + + LC_ALL=en_US.UTF-8 + export LC_ALL + + ( + bzr init bzrrepo && + cd bzrrepo && + + echo test >> "ærø" && + bzr add "ærø" && + echo test >> "ø~?" && + bzr add "ø~?" && + bzr commit -m add-utf-8 && + echo test >> "ærø" && + bzr commit -m test-utf-8 && + bzr rm "ø~?" && + bzr mv "ærø" "ø~?" && + bzr commit -m bzr-mv-utf-8 + ) && + + ( + git clone "bzr::bzrrepo" gitrepo && + cd gitrepo && + git -c core.quotepath=false ls-files > ../actual + ) && + echo "ø~?" > expected && + test_cmp expected actual ' test_expect_success 'push utf-8 filenames' ' - mkdir -p tmp && cd tmp && - test_when_finished "cd .. && rm -rf tmp && LC_ALL=C" && + test_when_finished "rm -rf bzrrepo gitrepo && LC_ALL=C" && + + mkdir -p tmp && cd tmp && - LC_ALL=en_US.UTF-8 - export LC_ALL + LC_ALL=en_US.UTF-8 + export LC_ALL - ( - bzr init bzrrepo && - cd bzrrepo && + ( + bzr init bzrrepo && + cd bzrrepo && - echo one >> content && - bzr add content && - bzr commit -m one - ) && + echo one >> content && + bzr add content && + bzr commit -m one + ) && - ( - git clone "bzr::$PWD/bzrrepo" gitrepo && - cd gitrepo && + ( + git clone "bzr::bzrrepo" gitrepo && + cd gitrepo && - echo test >> "ærø" && - git add "ærø" && - git commit -m utf-8 && + echo test >> "ærø" && + git add "ærø" && + git commit -m utf-8 && - git push - ) && + git push + ) && - (cd bzrrepo && bzr ls > ../actual) && - printf "content\nærø\n" > expected && - test_cmp expected actual + (cd bzrrepo && bzr ls > ../actual) && + printf "content\nærø\n" > expected && + test_cmp expected actual ' test_expect_success 'pushing a merge' ' - mkdir -p tmp && cd tmp && - test_when_finished "cd .. && rm -rf tmp" && - - ( - bzr init bzrrepo && - cd bzrrepo && - echo one > content && - bzr add content && - bzr commit -m one - ) && - - git clone "bzr::$PWD/bzrrepo" gitrepo && - - ( - cd bzrrepo && - echo two > content && - bzr commit -m two - ) && - - ( - cd gitrepo && - echo three > content && - git commit -a -m three && - git fetch && - git merge origin/master || true && - echo three > content && - git commit -a --no-edit && - git push - ) && - - echo three > expected && - cat bzrrepo/content > actual && - test_cmp expected actual + test_when_finished "rm -rf bzrrepo gitrepo" && + + ( + bzr init bzrrepo && + cd bzrrepo && + echo one > content && + bzr add content && + bzr commit -m one + ) && + + git clone "bzr::bzrrepo" gitrepo && + + ( + cd bzrrepo && + echo two > content && + bzr commit -m two + ) && + + ( + cd gitrepo && + echo three > content && + git commit -a -m three && + git fetch && + git merge origin/master || true && + echo three > content && + git commit -a --no-edit && + git push + ) && + + echo three > expected && + cat bzrrepo/content > actual && + test_cmp expected actual ' cat > expected <<EOF @@ -271,71 +292,70 @@ origin/trunk EOF test_expect_success 'proper bzr repo' ' - mkdir -p tmp && cd tmp && - test_when_finished "cd .. && rm -rf tmp" && - - bzr init-repo bzrrepo && - - bzr init bzrrepo/trunk && - ( - cd bzrrepo/trunk && - echo one >> content && - bzr add content && - bzr commit -m one - ) && - - bzr branch bzrrepo/trunk bzrrepo/branch && - ( - cd bzrrepo/branch && - echo two >> content && - bzr commit -m one - ) && - - git clone "bzr::$PWD/bzrrepo" gitrepo && - ( - cd gitrepo && - git for-each-ref --format "%(refname:short)" refs/remotes/origin > ../actual - ) && - - test_cmp ../expected actual + test_when_finished "rm -rf bzrrepo gitrepo" && + + bzr init-repo bzrrepo && + + ( + bzr init bzrrepo/trunk && + cd bzrrepo/trunk && + echo one >> content && + bzr add content && + bzr commit -m one + ) && + + ( + bzr branch bzrrepo/trunk bzrrepo/branch && + cd bzrrepo/branch && + echo two >> content && + bzr commit -m one + ) && + + ( + git clone "bzr::bzrrepo" gitrepo && + cd gitrepo && + git for-each-ref --format "%(refname:short)" refs/remotes/origin > ../actual + ) && + + test_cmp expected actual ' test_expect_success 'strip' ' - # Do not imitate this style; always chdir inside a subshell instead - mkdir -p tmp && cd tmp && - test_when_finished "cd .. && rm -rf tmp" && + test_when_finished "rm -rf bzrrepo gitrepo" && - ( - bzr init bzrrepo && - cd bzrrepo && + ( + bzr init bzrrepo && + cd bzrrepo && - echo one >> content && - bzr add content && - bzr commit -m one && + echo one >> content && + bzr add content && + bzr commit -m one && - echo two >> content && - bzr commit -m two - ) && + echo two >> content && + bzr commit -m two + ) && - git clone "bzr::$PWD/bzrrepo" gitrepo && + git clone "bzr::bzrrepo" gitrepo && - ( - cd bzrrepo && - bzr uncommit --force && + ( + cd bzrrepo && + bzr uncommit --force && - echo three >> content && - bzr commit -m three && + echo three >> content && + bzr commit -m three && - echo four >> content && - bzr commit -m four && - bzr log --line | sed -e "s/^[0-9][0-9]*: //" > ../expected - ) && + echo four >> content && + bzr commit -m four && + bzr log --line | sed -e "s/^[0-9][0-9]*: //" > ../expected + ) && - (cd gitrepo && - git fetch && - git log --format="%an %ad %s" --date=short origin/master > ../actual) && + ( + cd gitrepo && + git fetch && + git log --format="%an %ad %s" --date=short origin/master > ../actual + ) && - test_cmp expected actual + test_cmp expected actual ' test_done diff --git a/contrib/remote-helpers/test-hg-bidi.sh b/contrib/remote-helpers/test-hg-bidi.sh index f569697734..f83d67d74f 100755 --- a/contrib/remote-helpers/test-hg-bidi.sh +++ b/contrib/remote-helpers/test-hg-bidi.sh @@ -15,23 +15,22 @@ if ! test_have_prereq PYTHON; then test_done fi -if ! "$PYTHON_PATH" -c 'import mercurial'; then +if ! python -c 'import mercurial'; then skip_all='skipping remote-hg tests; mercurial not available' test_done fi # clone to a git repo git_clone () { - git clone -q "hg::$PWD/$1" $2 + git clone -q "hg::$1" $2 } # clone to an hg repo hg_clone () { ( hg init $2 && - hg -R $2 bookmark -i master && cd $1 && - git push -q "hg::$PWD/../$2" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' + git push -q "hg::../$2" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' ) && (cd $2 && hg -q update) @@ -41,17 +40,15 @@ hg_clone () { hg_push () { ( cd $2 - old=$(git symbolic-ref --short HEAD) git checkout -q -b tmp && - git fetch -q "hg::$PWD/../$1" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' && - git checkout -q $old && + git fetch -q "hg::../$1" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' && + git checkout -q @{-1} && git branch -q -D tmp 2> /dev/null || true ) } hg_log () { - hg -R $1 log --graph --debug >log && - grep -v 'tag: *default/' log + hg -R $1 log --graph --debug } setup () { @@ -67,6 +64,7 @@ setup () { echo "graphlog =" ) >> "$HOME"/.hgrc && git config --global remote-hg.hg-git-compat true + git config --global remote-hg.track-branches true HGEDITOR=/usr/bin/true GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0230" @@ -77,8 +75,7 @@ setup () { setup test_expect_success 'encoding' ' - mkdir -p tmp && cd tmp && - test_when_finished "cd .. && rm -rf tmp" && + test_when_finished "rm -rf gitrepo* hgrepo*" && ( git init -q gitrepo && @@ -115,8 +112,7 @@ test_expect_success 'encoding' ' ' test_expect_success 'file removal' ' - mkdir -p tmp && cd tmp && - test_when_finished "cd .. && rm -rf tmp" && + test_when_finished "rm -rf gitrepo* hgrepo*" && ( git init -q gitrepo && @@ -148,8 +144,7 @@ test_expect_success 'file removal' ' ' test_expect_success 'git tags' ' - mkdir -p tmp && cd tmp && - test_when_finished "cd .. && rm -rf tmp" && + test_when_finished "rm -rf gitrepo* hgrepo*" && ( git init -q gitrepo && @@ -177,8 +172,7 @@ test_expect_success 'git tags' ' ' test_expect_success 'hg branch' ' - mkdir -p tmp && cd tmp && - test_when_finished "cd .. && rm -rf tmp" && + test_when_finished "rm -rf gitrepo* hgrepo*" && ( git init -q gitrepo && @@ -194,7 +188,7 @@ test_expect_success 'hg branch' ' hg_clone gitrepo hgrepo && cd hgrepo && - hg -q co master && + hg -q co default && hg mv alpha beta && hg -q commit -m "rename alpha to beta" && hg branch gamma | grep -v "permanent and global" && @@ -214,8 +208,7 @@ test_expect_success 'hg branch' ' ' test_expect_success 'hg tags' ' - mkdir -p tmp && cd tmp && - test_when_finished "cd .. && rm -rf tmp" && + test_when_finished "rm -rf gitrepo* hgrepo*" && ( git init -q gitrepo && @@ -231,7 +224,7 @@ test_expect_success 'hg tags' ' hg_clone gitrepo hgrepo && cd hgrepo && - hg co master && + hg co default && hg tag alpha ) && diff --git a/contrib/remote-helpers/test-hg-hg-git.sh b/contrib/remote-helpers/test-hg-hg-git.sh index 7f579c8436..2219284382 100755 --- a/contrib/remote-helpers/test-hg-hg-git.sh +++ b/contrib/remote-helpers/test-hg-hg-git.sh @@ -15,19 +15,20 @@ if ! test_have_prereq PYTHON; then test_done fi -if ! "$PYTHON_PATH" -c 'import mercurial'; then +if ! python -c 'import mercurial'; then skip_all='skipping remote-hg tests; mercurial not available' test_done fi -if ! "$PYTHON_PATH" -c 'import hggit'; then +if ! python -c 'import hggit'; then skip_all='skipping remote-hg tests; hg-git not available' test_done fi # clone to a git repo with git git_clone_git () { - git clone -q "hg::$PWD/$1" $2 + git clone -q "hg::$1" $2 && + (cd $2 && git checkout master && git branch -D default) } # clone to an hg repo with git @@ -36,7 +37,7 @@ hg_clone_git () { hg init $2 && hg -R $2 bookmark -i master && cd $1 && - git push -q "hg::$PWD/../$2" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' + git push -q "hg::../$2" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' ) && (cd $2 && hg -q update) @@ -61,10 +62,10 @@ hg_clone_hg () { hg_push_git () { ( cd $2 - old=$(git symbolic-ref --short HEAD) git checkout -q -b tmp && - git fetch -q "hg::$PWD/../$1" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' && - git checkout -q $old && + git fetch -q "hg::../$1" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' && + git branch -D default && + git checkout -q @{-1} && git branch -q -D tmp 2> /dev/null || true ) } @@ -104,18 +105,18 @@ setup () { git config --global remote-hg.hg-git-compat true git config --global remote-hg.track-branches false - HGEDITOR=/usr/bin/true + HGEDITOR=true + HGMERGE=true GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0230" GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" - export HGEDITOR GIT_AUTHOR_DATE GIT_COMMITTER_DATE + export HGEDITOR HGMERGE GIT_AUTHOR_DATE GIT_COMMITTER_DATE } setup test_expect_success 'executable bit' ' - mkdir -p tmp && cd tmp && - test_when_finished "cd .. && rm -rf tmp" && + test_when_finished "rm -rf gitrepo* hgrepo*" && ( git init -q gitrepo && @@ -150,8 +151,7 @@ test_expect_success 'executable bit' ' ' test_expect_success 'symlink' ' - mkdir -p tmp && cd tmp && - test_when_finished "cd .. && rm -rf tmp" && + test_when_finished "rm -rf gitrepo* hgrepo*" && ( git init -q gitrepo && @@ -181,8 +181,7 @@ test_expect_success 'symlink' ' ' test_expect_success 'merge conflict 1' ' - mkdir -p tmp && cd tmp && - test_when_finished "cd .. && rm -rf tmp" && + test_when_finished "rm -rf gitrepo* hgrepo*" && ( hg init hgrepo1 && @@ -198,7 +197,7 @@ test_expect_success 'merge conflict 1' ' echo C > afile && hg ci -m "A->C" && - hg merge -r1 || true && + hg merge -r1 && echo C > afile && hg resolve -m afile && hg ci -m "merge to C" @@ -216,8 +215,7 @@ test_expect_success 'merge conflict 1' ' ' test_expect_success 'merge conflict 2' ' - mkdir -p tmp && cd tmp && - test_when_finished "cd .. && rm -rf tmp" && + test_when_finished "rm -rf gitrepo* hgrepo*" && ( hg init hgrepo1 && @@ -251,8 +249,7 @@ test_expect_success 'merge conflict 2' ' ' test_expect_success 'converged merge' ' - mkdir -p tmp && cd tmp && - test_when_finished "cd .. && rm -rf tmp" && + test_when_finished "rm -rf gitrepo* hgrepo*" && ( hg init hgrepo1 && @@ -287,8 +284,7 @@ test_expect_success 'converged merge' ' ' test_expect_success 'encoding' ' - mkdir -p tmp && cd tmp && - test_when_finished "cd .. && rm -rf tmp" && + test_when_finished "rm -rf gitrepo* hgrepo*" && ( git init -q gitrepo && @@ -327,8 +323,7 @@ test_expect_success 'encoding' ' ' test_expect_success 'file removal' ' - mkdir -p tmp && cd tmp && - test_when_finished "cd .. && rm -rf tmp" && + test_when_finished "rm -rf gitrepo* hgrepo*" && ( git init -q gitrepo && @@ -367,8 +362,7 @@ test_expect_success 'file removal' ' ' test_expect_success 'git tags' ' - mkdir -p tmp && cd tmp && - test_when_finished "cd .. && rm -rf tmp" && + test_when_finished "rm -rf gitrepo* hgrepo*" && ( git init -q gitrepo && @@ -394,8 +388,7 @@ test_expect_success 'git tags' ' ' test_expect_success 'hg author' ' - mkdir -p tmp && cd tmp && - test_when_finished "cd .. && rm -rf tmp" && + test_when_finished "rm -rf gitrepo* hgrepo*" && for x in hg git; do ( @@ -461,8 +454,7 @@ test_expect_success 'hg author' ' ' test_expect_success 'hg branch' ' - mkdir -p tmp && cd tmp && - test_when_finished "cd .. && rm -rf tmp" && + test_when_finished "rm -rf gitrepo* hgrepo*" && for x in hg git; do ( @@ -498,8 +490,7 @@ test_expect_success 'hg branch' ' ' test_expect_success 'hg tags' ' - mkdir -p tmp && cd tmp && - test_when_finished "cd .. && rm -rf tmp" && + test_when_finished "rm -rf gitrepo* hgrepo*" && for x in hg git; do ( diff --git a/contrib/remote-helpers/test-hg.sh b/contrib/remote-helpers/test-hg.sh index 8de2aa7fec..f7ce8aa853 100755 --- a/contrib/remote-helpers/test-hg.sh +++ b/contrib/remote-helpers/test-hg.sh @@ -15,143 +15,678 @@ if ! test_have_prereq PYTHON; then test_done fi -if ! "$PYTHON_PATH" -c 'import mercurial'; then +if ! python -c 'import mercurial'; then skip_all='skipping remote-hg tests; mercurial not available' test_done fi check () { - (cd $1 && - git log --format='%s' -1 && - git symbolic-ref HEAD) > actual && - (echo $2 && - echo "refs/heads/$3") > expected && + echo $3 > expected && + git --git-dir=$1/.git log --format='%s' -1 $2 > actual test_cmp expected actual } +check_branch () { + if [ -n "$3" ]; then + echo $3 > expected && + hg -R $1 log -r $2 --template '{desc}\n' > actual && + test_cmp expected actual + else + hg -R $1 branches > out && + ! grep $2 out + fi +} + +check_bookmark () { + if [ -n "$3" ]; then + echo $3 > expected && + hg -R $1 log -r "bookmark('$2')" --template '{desc}\n' > actual && + test_cmp expected actual + else + hg -R $1 bookmarks > out && + ! grep $2 out + fi +} + +check_push () { + local expected_ret=$1 ret=0 ref_ret=0 IFS=':' + + shift + git push origin "$@" 2> error + ret=$? + cat error + + while read branch kind + do + case "$kind" in + 'new') + grep "^ \* \[new branch\] *${branch} -> ${branch}$" error || ref_ret=1 + ;; + 'non-fast-forward') + grep "^ ! \[rejected\] *${branch} -> ${branch} (non-fast-forward)$" error || ref_ret=1 + ;; + 'fetch-first') + grep "^ ! \[rejected\] *${branch} -> ${branch} (fetch first)$" error || ref_ret=1 + ;; + 'forced-update') + grep "^ + [a-f0-9]*\.\.\.[a-f0-9]* *${branch} -> ${branch} (forced update)$" error || ref_ret=1 + ;; + '') + grep "^ [a-f0-9]*\.\.[a-f0-9]* *${branch} -> ${branch}$" error || ref_ret=1 + ;; + esac + let 'ref_ret' && echo "match for '$branch' failed" && break + done + + if let 'expected_ret != ret || ref_ret' + then + return 1 + fi + + return 0 +} + setup () { ( echo "[ui]" echo "username = H G Wells <wells@example.com>" - ) >> "$HOME"/.hgrc + echo "[extensions]" + echo "mq =" + ) >> "$HOME"/.hgrc && + + GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0230" && + GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" && + export GIT_COMMITTER_DATE GIT_AUTHOR_DATE } setup test_expect_success 'cloning' ' - test_when_finished "rm -rf gitrepo*" && - - ( - hg init hgrepo && - cd hgrepo && - echo zero > content && - hg add content && - hg commit -m zero - ) && + test_when_finished "rm -rf gitrepo*" && - git clone "hg::$PWD/hgrepo" gitrepo && - check gitrepo zero master + ( + hg init hgrepo && + cd hgrepo && + echo zero > content && + hg add content && + hg commit -m zero + ) && + + git clone "hg::hgrepo" gitrepo && + check gitrepo HEAD zero ' test_expect_success 'cloning with branches' ' - test_when_finished "rm -rf gitrepo*" && + test_when_finished "rm -rf gitrepo*" && + + ( + cd hgrepo && + hg branch next && + echo next > content && + hg commit -m next + ) && + + git clone "hg::hgrepo" gitrepo && + check gitrepo origin/branches/next next +' + +test_expect_success 'cloning with bookmarks' ' + test_when_finished "rm -rf gitrepo*" && + + ( + cd hgrepo && + hg checkout default && + hg bookmark feature-a && + echo feature-a > content && + hg commit -m feature-a + ) && + + git clone "hg::hgrepo" gitrepo && + check gitrepo origin/feature-a feature-a +' + +test_expect_success 'update bookmark' ' + test_when_finished "rm -rf gitrepo*" && - ( - cd hgrepo && - hg branch next && - echo next > content && - hg commit -m next - ) && + ( + cd hgrepo && + hg bookmark devel + ) && - git clone "hg::$PWD/hgrepo" gitrepo && - check gitrepo next next && + ( + git clone "hg::hgrepo" gitrepo && + cd gitrepo && + git checkout --quiet devel && + echo devel > content && + git commit -a -m devel && + git push --quiet + ) && + + check_bookmark hgrepo devel devel +' - (cd hgrepo && hg checkout default) && +test_expect_success 'new bookmark' ' + test_when_finished "rm -rf gitrepo*" && - git clone "hg::$PWD/hgrepo" gitrepo2 && - check gitrepo2 zero master + ( + git clone "hg::hgrepo" gitrepo && + cd gitrepo && + git checkout --quiet -b feature-b && + echo feature-b > content && + git commit -a -m feature-b && + git push --quiet origin feature-b + ) && + + check_bookmark hgrepo feature-b feature-b ' -test_expect_success 'cloning with bookmarks' ' - test_when_finished "rm -rf gitrepo*" && +# cleanup previous stuff +rm -rf hgrepo + +author_test () { + echo $1 >> content && + hg commit -u "$2" -m "add $1" && + echo "$3" >> ../expected +} - ( - cd hgrepo && - hg bookmark feature-a && - echo feature-a > content && - hg commit -m feature-a - ) && +test_expect_success 'authors' ' + test_when_finished "rm -rf hgrepo gitrepo" && + + ( + hg init hgrepo && + cd hgrepo && + + touch content && + hg add content && + + > ../expected && + author_test alpha "" "H G Wells <wells@example.com>" && + author_test beta "test" "test <unknown>" && + author_test beta "test <test@example.com> (comment)" "test <test@example.com>" && + author_test gamma "<test@example.com>" "Unknown <test@example.com>" && + author_test delta "name<test@example.com>" "name <test@example.com>" && + author_test epsilon "name <test@example.com" "name <test@example.com>" && + author_test zeta " test " "test <unknown>" && + author_test eta "test < test@example.com >" "test <test@example.com>" && + author_test theta "test >test@example.com>" "test <test@example.com>" && + author_test iota "test < test <at> example <dot> com>" "test <unknown>" && + author_test kappa "test@example.com" "Unknown <test@example.com>" + ) && + + git clone "hg::hgrepo" gitrepo && + git --git-dir=gitrepo/.git log --reverse --format="%an <%ae>" > actual && - git clone "hg::$PWD/hgrepo" gitrepo && - check gitrepo feature-a feature-a + test_cmp expected actual ' -test_expect_success 'cloning with detached head' ' - test_when_finished "rm -rf gitrepo*" && +test_expect_success 'strip' ' + test_when_finished "rm -rf hgrepo gitrepo" && + + ( + hg init hgrepo && + cd hgrepo && + + echo one >> content && + hg add content && + hg commit -m one && + + echo two >> content && + hg commit -m two + ) && + + git clone "hg::hgrepo" gitrepo && + + ( + cd hgrepo && + hg strip 1 && - ( - cd hgrepo && - hg update -r 0 - ) && + echo three >> content && + hg commit -m three && - git clone "hg::$PWD/hgrepo" gitrepo && - check gitrepo zero master + echo four >> content && + hg commit -m four + ) && + + ( + cd gitrepo && + git fetch && + git log --format="%s" origin/master > ../actual + ) && + + hg -R hgrepo log --template "{desc}\n" > expected && + test_cmp actual expected ' -test_expect_success 'update bookmark' ' - test_when_finished "rm -rf gitrepo*" && +test_expect_success 'remote push with master bookmark' ' + test_when_finished "rm -rf hgrepo gitrepo*" && - ( - cd hgrepo && - hg bookmark devel - ) && + ( + hg init hgrepo && + cd hgrepo && + echo zero > content && + hg add content && + hg commit -m zero && + hg bookmark master && + echo one > content && + hg commit -m one + ) && - ( - git clone "hg::$PWD/hgrepo" gitrepo && - cd gitrepo && - git checkout devel && - echo devel > content && - git commit -a -m devel && - git push - ) && + ( + git clone "hg::hgrepo" gitrepo && + cd gitrepo && + echo two > content && + git commit -a -m two && + git push + ) && + + check_branch hgrepo default two +' - hg -R hgrepo bookmarks | egrep "devel[ ]+3:" +cat > expected <<EOF +changeset: 0:6e2126489d3d +tag: tip +user: A U Thor <author@example.com> +date: Mon Jan 01 00:00:00 2007 +0230 +summary: one + +EOF + +test_expect_success 'remote push from master branch' ' + test_when_finished "rm -rf hgrepo gitrepo*" && + + hg init hgrepo && + + ( + git init gitrepo && + cd gitrepo && + git remote add origin "hg::../hgrepo" && + echo one > content && + git add content && + git commit -a -m one && + git push origin master + ) && + + hg -R hgrepo log > actual && + cat actual && + test_cmp expected actual && + + check_branch hgrepo default one ' -author_test () { - echo $1 >> content && - hg commit -u "$2" -m "add $1" && - echo "$3" >> ../expected +GIT_REMOTE_HG_TEST_REMOTE=1 +export GIT_REMOTE_HG_TEST_REMOTE + +test_expect_success 'remote cloning' ' + test_when_finished "rm -rf gitrepo*" && + + ( + hg init hgrepo && + cd hgrepo && + echo zero > content && + hg add content && + hg commit -m zero + ) && + + git clone "hg::hgrepo" gitrepo && + check gitrepo HEAD zero +' + +test_expect_success 'remote update bookmark' ' + test_when_finished "rm -rf gitrepo*" && + + ( + cd hgrepo && + hg bookmark devel + ) && + + ( + git clone "hg::hgrepo" gitrepo && + cd gitrepo && + git checkout --quiet devel && + echo devel > content && + git commit -a -m devel && + git push --quiet + ) && + + check_bookmark hgrepo devel devel +' + +test_expect_success 'remote new bookmark' ' + test_when_finished "rm -rf gitrepo*" && + + ( + git clone "hg::hgrepo" gitrepo && + cd gitrepo && + git checkout --quiet -b feature-b && + echo feature-b > content && + git commit -a -m feature-b && + git push --quiet origin feature-b + ) && + + check_bookmark hgrepo feature-b feature-b +' + +test_expect_success 'remote push diverged' ' + test_when_finished "rm -rf gitrepo*" && + + git clone "hg::hgrepo" gitrepo && + + ( + cd hgrepo && + hg checkout default && + echo bump > content && + hg commit -m bump + ) && + + ( + cd gitrepo && + echo diverge > content && + git commit -a -m diverged && + check_push 1 <<-EOF + master:non-fast-forward + EOF + ) && + + check_branch hgrepo default bump +' + +test_expect_success 'remote update bookmark diverge' ' + test_when_finished "rm -rf gitrepo*" && + + ( + cd hgrepo && + hg checkout tip^ && + hg bookmark diverge + ) && + + git clone "hg::hgrepo" gitrepo && + + ( + cd hgrepo && + echo "bump bookmark" > content && + hg commit -m "bump bookmark" + ) && + + ( + cd gitrepo && + git checkout --quiet diverge && + echo diverge > content && + git commit -a -m diverge && + check_push 1 <<-EOF + diverge:fetch-first + EOF + ) && + + check_bookmark hgrepo diverge "bump bookmark" +' + +test_expect_success 'remote new bookmark multiple branch head' ' + test_when_finished "rm -rf gitrepo*" && + + ( + git clone "hg::hgrepo" gitrepo && + cd gitrepo && + git checkout --quiet -b feature-c HEAD^ && + echo feature-c > content && + git commit -a -m feature-c && + git push --quiet origin feature-c + ) && + + check_bookmark hgrepo feature-c feature-c +' + +# cleanup previous stuff +rm -rf hgrepo + +setup_big_push () { + ( + hg init hgrepo && + cd hgrepo && + echo zero > content && + hg add content && + hg commit -m zero && + hg bookmark bad_bmark1 && + echo one > content && + hg commit -m one && + hg bookmark bad_bmark2 && + hg bookmark good_bmark && + hg bookmark -i good_bmark && + hg -q branch good_branch && + echo "good branch" > content && + hg commit -m "good branch" && + hg -q branch bad_branch && + echo "bad branch" > content && + hg commit -m "bad branch" + ) && + + git clone "hg::hgrepo" gitrepo && + + ( + cd gitrepo && + echo two > content && + git commit -q -a -m two && + + git checkout -q good_bmark && + echo three > content && + git commit -q -a -m three && + + git checkout -q bad_bmark1 && + git reset --hard HEAD^ && + echo four > content && + git commit -q -a -m four && + + git checkout -q bad_bmark2 && + git reset --hard HEAD^ && + echo five > content && + git commit -q -a -m five && + + git checkout -q -b new_bmark master && + echo six > content && + git commit -q -a -m six && + + git checkout -q branches/good_branch && + echo seven > content && + git commit -q -a -m seven && + echo eight > content && + git commit -q -a -m eight && + + git checkout -q branches/bad_branch && + git reset --hard HEAD^ && + echo nine > content && + git commit -q -a -m nine && + + git checkout -q -b branches/new_branch master && + echo ten > content && + git commit -q -a -m ten + ) } -test_expect_success 'authors' ' - mkdir -p tmp && cd tmp && - test_when_finished "cd .. && rm -rf tmp" && - - ( - hg init hgrepo && - cd hgrepo && - - touch content && - hg add content && - - author_test alpha "" "H G Wells <wells@example.com>" && - author_test beta "test" "test <unknown>" && - author_test beta "test <test@example.com> (comment)" "test <test@example.com>" && - author_test gamma "<test@example.com>" "Unknown <test@example.com>" && - author_test delta "name<test@example.com>" "name <test@example.com>" && - author_test epsilon "name <test@example.com" "name <test@example.com>" && - author_test zeta " test " "test <unknown>" && - author_test eta "test < test@example.com >" "test <test@example.com>" && - author_test theta "test >test@example.com>" "test <test@example.com>" && - author_test iota "test < test <at> example <dot> com>" "test <unknown>" && - author_test kappa "test@example.com" "Unknown <test@example.com>" - ) && - - git clone "hg::$PWD/hgrepo" gitrepo && - git --git-dir=gitrepo/.git log --reverse --format="%an <%ae>" > actual && - - test_cmp expected actual +test_expect_success 'remote big push' ' + test_when_finished "rm -rf hgrepo gitrepo*" && + + setup_big_push + + ( + cd gitrepo && + + check_push 1 --all <<-EOF + master + good_bmark + branches/good_branch + new_bmark:new + branches/new_branch:new + bad_bmark1:non-fast-forward + bad_bmark2:non-fast-forward + branches/bad_branch:non-fast-forward + EOF + ) && + + check_branch hgrepo default one && + check_branch hgrepo good_branch "good branch" && + check_branch hgrepo bad_branch "bad branch" && + check_branch hgrepo new_branch '' && + check_bookmark hgrepo good_bmark one && + check_bookmark hgrepo bad_bmark1 one && + check_bookmark hgrepo bad_bmark2 one && + check_bookmark hgrepo new_bmark '' +' + +test_expect_success 'remote big push fetch first' ' + test_when_finished "rm -rf hgrepo gitrepo*" && + + ( + hg init hgrepo && + cd hgrepo && + echo zero > content && + hg add content && + hg commit -m zero && + hg bookmark bad_bmark && + hg bookmark good_bmark && + hg bookmark -i good_bmark && + hg -q branch good_branch && + echo "good branch" > content && + hg commit -m "good branch" && + hg -q branch bad_branch && + echo "bad branch" > content && + hg commit -m "bad branch" + ) && + + git clone "hg::hgrepo" gitrepo && + + ( + cd hgrepo && + hg bookmark -f bad_bmark && + echo update_bmark > content && + hg commit -m "update bmark" + ) && + + ( + cd gitrepo && + echo two > content && + git commit -q -a -m two && + + git checkout -q good_bmark && + echo three > content && + git commit -q -a -m three && + + git checkout -q bad_bmark && + echo four > content && + git commit -q -a -m four && + + git checkout -q branches/bad_branch && + echo five > content && + git commit -q -a -m five && + + check_push 1 --all <<-EOF + master + good_bmark + new_bmark:new + new_branch:new + bad_bmark:fetch-first + branches/bad_branch:festch-first + EOF + + git fetch && + + check_push 1 --all <<-EOF + master + good_bmark + bad_bmark:non-fast-forward + branches/bad_branch:non-fast-forward + EOF + ) +' + +test_expect_failure 'remote big push force' ' + test_when_finished "rm -rf hgrepo gitrepo*" && + + setup_big_push + + ( + cd gitrepo && + + check_push 0 --force --all <<-EOF + master + good_bmark + branches/good_branch + new_bmark:new + branches/new_branch:new + bad_bmark1:forced-update + bad_bmark2:forced-update + branches/bad_branch:forced-update + EOF + ) && + + check_branch hgrepo default six && + check_branch hgrepo good_branch eight && + check_branch hgrepo bad_branch nine && + check_branch hgrepo new_branch ten && + check_bookmark hgrepo good_bmark three && + check_bookmark hgrepo bad_bmark1 four && + check_bookmark hgrepo bad_bmark2 five && + check_bookmark hgrepo new_bmark six +' + +test_expect_failure 'remote big push dry-run' ' + test_when_finished "rm -rf hgrepo gitrepo*" && + + setup_big_push + + ( + cd gitrepo && + + check_push 0 --dry-run --all <<-EOF + master + good_bmark + branches/good_branch + new_bmark:new + branches/new_branch:new + bad_bmark1:non-fast-forward + bad_bmark2:non-fast-forward + branches/bad_branch:non-fast-forward + EOF + + check_push 0 --dry-run master good_bmark new_bmark branches/good_branch branches/new_branch <<-EOF + master + good_bmark + branches/good_branch + new_bmark:new + branches/new_branch:new + EOF + ) && + + check_branch hgrepo default one && + check_branch hgrepo good_branch "good branch" && + check_branch hgrepo bad_branch "bad branch" && + check_branch hgrepo new_branch '' && + check_bookmark hgrepo good_bmark one && + check_bookmark hgrepo bad_bmark1 one && + check_bookmark hgrepo bad_bmark2 one && + check_bookmark hgrepo new_bmark '' +' + +test_expect_success 'remote double failed push' ' + test_when_finished "rm -rf hgrepo gitrepo*" && + + ( + hg init hgrepo && + cd hgrepo && + echo zero > content && + hg add content && + hg commit -m zero && + echo one > content && + hg commit -m one + ) && + + ( + git clone "hg::hgrepo" gitrepo && + cd gitrepo && + git reset --hard HEAD^ && + echo two > content && + git commit -a -m two && + test_expect_code 1 git push && + test_expect_code 1 git push + ) ' test_done diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh index 8a23f58ba0..51ae932e5e 100755 --- a/contrib/subtree/git-subtree.sh +++ b/contrib/subtree/git-subtree.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh # # git-subtree.sh: split/join git repositories in subdirectories of this one # @@ -715,7 +715,8 @@ cmd_push() repository=$1 refspec=$2 echo "git push using: " $repository $refspec - git push $repository $(git subtree split --prefix=$prefix):refs/heads/$refspec + localrev=$(git subtree split --prefix="$prefix") || die + git push $repository $localrev:refs/heads/$refspec else die "'$dir' must already exist. Try 'git subtree add'." fi |