diff options
Diffstat (limited to 'contrib')
-rw-r--r-- | contrib/completion/git-completion.bash | 110 | ||||
-rw-r--r-- | contrib/completion/git-completion.zsh | 136 | ||||
-rw-r--r-- | contrib/completion/git-prompt.sh | 23 | ||||
-rwxr-xr-x | contrib/remote-helpers/git-remote-bzr | 390 | ||||
-rwxr-xr-x | contrib/remote-helpers/git-remote-hg | 307 | ||||
-rwxr-xr-x | contrib/remote-helpers/test-bzr.sh | 110 | ||||
-rwxr-xr-x | contrib/remote-helpers/test-hg-bidi.sh | 22 | ||||
-rwxr-xr-x | contrib/remote-helpers/test-hg-hg-git.sh | 21 | ||||
-rwxr-xr-x | contrib/remote-helpers/test-hg.sh | 36 |
9 files changed, 908 insertions, 247 deletions
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 2ba1461422..1c35eef56a 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -53,19 +53,6 @@ __gitdir () fi } -__gitcomp_1 () -{ - local c IFS=$' \t\n' - for c in $1; do - c="$c$2" - case $c in - --*=*|*.) ;; - *) c="$c " ;; - esac - printf '%s\n' "$c" - done -} - # The following function is based on code from: # # bash_completion - programmable completion functions for bash 3.2+ @@ -195,8 +182,18 @@ _get_comp_words_by_ref () } fi -# Generates completion reply with compgen, appending a space to possible -# completion words, if necessary. +__gitcompadd () +{ + local i=0 + for x in $1; do + if [[ "$x" == "$3"* ]]; then + COMPREPLY[i++]="$2$x$4" + fi + done +} + +# Generates completion reply, appending a space to possible completion words, +# if necessary. # It accepts 1 to 4 arguments: # 1: List of possible completion words. # 2: A prefix to be added to each possible completion word (optional). @@ -208,19 +205,25 @@ __gitcomp () case "$cur_" in --*=) - COMPREPLY=() ;; *) - local IFS=$'\n' - COMPREPLY=($(compgen -P "${2-}" \ - -W "$(__gitcomp_1 "${1-}" "${4-}")" \ - -- "$cur_")) + local c i=0 IFS=$' \t\n' + for c in $1; do + c="$c${4-}" + if [[ $c == "$cur_"* ]]; then + case $c in + --*=*|*.) ;; + *) c="$c " ;; + esac + COMPREPLY[i++]="${2-}$c" + fi + done ;; esac } -# Generates completion reply with compgen from newline-separated possible -# completion words by appending a space to all of them. +# Generates completion reply from newline-separated possible completion words +# by appending a space to all of them. # It accepts 1 to 4 arguments: # 1: List of possible completion words, separated by a single newline. # 2: A prefix to be added to each possible completion word (optional). @@ -231,7 +234,7 @@ __gitcomp () __gitcomp_nl () { local IFS=$'\n' - COMPREPLY=($(compgen -P "${2-}" -S "${4- }" -W "$1" -- "${3-$cur}")) + __gitcompadd "$1" "${2-}" "${3-$cur}" "${4- }" } # Generates completion reply with compgen from newline-separated possible @@ -614,7 +617,6 @@ __git_complete_remote_or_refspec () case "$cmd" in push) no_complete_refspec=1 ;; fetch) - COMPREPLY=() return ;; *) ;; @@ -630,7 +632,6 @@ __git_complete_remote_or_refspec () return fi if [ $no_complete_refspec = 1 ]; then - COMPREPLY=() return fi [ "$remote" = "." ] && remote= @@ -951,7 +952,6 @@ _git_am () " return esac - COMPREPLY=() } _git_apply () @@ -971,7 +971,6 @@ _git_apply () " return esac - COMPREPLY=() } _git_add () @@ -1031,7 +1030,6 @@ _git_bisect () __gitcomp_nl "$(__git_refs)" ;; *) - COMPREPLY=() ;; esac } @@ -1124,9 +1122,14 @@ _git_cherry () _git_cherry_pick () { + local dir="$(__gitdir)" + if [ -f "$dir"/CHERRY_PICK_HEAD ]; then + __gitcomp "--continue --quit --abort" + return + fi case "$cur" in --*) - __gitcomp "--edit --no-commit" + __gitcomp "--edit --no-commit --signoff --strategy= --mainline" ;; *) __gitcomp_nl "$(__git_refs)" @@ -1170,7 +1173,6 @@ _git_clone () return ;; esac - COMPREPLY=() } _git_commit () @@ -1182,13 +1184,6 @@ _git_commit () ;; esac - case "$prev" in - -c|-C) - __gitcomp_nl "$(__git_refs)" "" "${cur}" - return - ;; - esac - case "$cur" in --cleanup=*) __gitcomp "default strip verbatim whitespace @@ -1319,11 +1314,12 @@ _git_fetch () } __git_format_patch_options=" - --stdout --attach --no-attach --thread --thread= --output-directory + --stdout --attach --no-attach --thread --thread= --no-thread --numbered --start-number --numbered-files --keep-subject --signoff --signature --no-signature --in-reply-to= --cc= --full-index --binary --not --all --cover-letter --no-prefix --src-prefix= --dst-prefix= --inline --suffix= --ignore-if-in-upstream --subject-prefix= + --output-directory --reroll-count --to= --quiet --notes " _git_format_patch () @@ -1354,7 +1350,6 @@ _git_fsck () return ;; esac - COMPREPLY=() } _git_gc () @@ -1365,7 +1360,6 @@ _git_gc () return ;; esac - COMPREPLY=() } _git_gitk () @@ -1442,7 +1436,6 @@ _git_init () return ;; esac - COMPREPLY=() } _git_ls_files () @@ -1578,7 +1571,6 @@ _git_mergetool () return ;; esac - COMPREPLY=() } _git_merge_base () @@ -1819,7 +1811,7 @@ __git_config_get_set_variables () _git_config () { case "$prev" in - branch.*.remote) + branch.*.remote|branch.*.pushremote) __gitcomp_nl "$(__git_remotes)" return ;; @@ -1827,11 +1819,19 @@ _git_config () __gitcomp_nl "$(__git_refs)" return ;; + branch.*.rebase) + __gitcomp "false true" + return + ;; + remote.pushdefault) + __gitcomp_nl "$(__git_remotes)" + return + ;; remote.*.fetch) local remote="${prev#remote.}" remote="${remote%.fetch}" if [ -z "$cur" ]; then - COMPREPLY=("refs/heads/") + __gitcomp_nl "refs/heads/" "" "" "" return fi __gitcomp_nl "$(__git_refs_remotes "$remote")" @@ -1866,6 +1866,10 @@ _git_config () " return ;; + diff.submodule) + __gitcomp "log short" + return + ;; help.format) __gitcomp "man info web html" return @@ -1891,7 +1895,6 @@ _git_config () return ;; *.*) - COMPREPLY=() return ;; esac @@ -1908,7 +1911,7 @@ _git_config () ;; branch.*.*) local pfx="${cur%.*}." cur_="${cur##*.}" - __gitcomp "remote merge mergeoptions rebase" "$pfx" "$cur_" + __gitcomp "remote pushremote merge mergeoptions rebase" "$pfx" "$cur_" return ;; branch.*) @@ -2061,13 +2064,14 @@ _git_config () core.whitespace core.worktree diff.autorefreshindex - diff.statGraphWidth diff.external diff.ignoreSubmodules diff.mnemonicprefix diff.noprefix diff.renameLimit diff.renames + diff.statGraphWidth + diff.submodule diff.suppressBlankEmpty diff.tool diff.wordRegex @@ -2202,6 +2206,7 @@ _git_config () receive.fsckObjects receive.unpackLimit receive.updateserverinfo + remote.pushdefault remotes. repack.usedeltabaseoffset rerere.autoupdate @@ -2272,7 +2277,6 @@ _git_remote () __gitcomp "$c" ;; *) - COMPREPLY=() ;; esac } @@ -2388,8 +2392,6 @@ _git_stash () *) if [ -z "$(__git_find_on_cmdline "$save_opts")" ]; then __gitcomp "$subcommands" - else - COMPREPLY=() fi ;; esac @@ -2402,14 +2404,12 @@ _git_stash () __gitcomp "--index --quiet" ;; show,--*|drop,--*|branch,--*) - COMPREPLY=() ;; show,*|apply,*|drop,*|pop,*|branch,*) __gitcomp_nl "$(git --git-dir="$(__gitdir)" stash list \ | sed -n -e 's/:.*//p')" ;; *) - COMPREPLY=() ;; esac fi @@ -2451,7 +2451,7 @@ _git_svn () --no-metadata --use-svm-props --use-svnsync-props --log-window-size= --no-checkout --quiet --repack-flags --use-log-author --localtime - --ignore-paths= $remote_opts + --ignore-paths= --include-paths= $remote_opts " local init_opts=" --template= --shared= --trunk= --tags= @@ -2526,7 +2526,6 @@ _git_svn () __gitcomp "--revision= --parent" ;; *) - COMPREPLY=() ;; esac fi @@ -2551,13 +2550,10 @@ _git_tag () case "$prev" in -m|-F) - COMPREPLY=() ;; -*|tag) if [ $f = 1 ]; then __gitcomp_nl "$(__git_tags)" - else - COMPREPLY=() fi ;; *) diff --git a/contrib/completion/git-completion.zsh b/contrib/completion/git-completion.zsh index cf8116d477..2565d2eef4 100644 --- a/contrib/completion/git-completion.zsh +++ b/contrib/completion/git-completion.zsh @@ -2,6 +2,8 @@ # zsh completion wrapper for git # +# 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. # @@ -21,6 +23,9 @@ complete () return 0 } +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 ZSH_VERSION='' . "$script" @@ -69,18 +74,131 @@ __gitcomp_file () compadd -Q -p "${2-}" -f -- ${=1} && _ret=0 } +__git_zsh_bash_func () +{ + emulate -L ksh + + local command=$1 + + local completion_func="_git_${command//-/_}" + declare -f $completion_func >/dev/null && $completion_func && return + + local expansion=$(__git_aliased_command "$command") + if [ -n "$expansion" ]; then + completion_func="_git_${expansion//-/_}" + declare -f $completion_func >/dev/null && $completion_func + fi +} + +__git_zsh_cmd_common () +{ + local -a list + list=( + add:'add file contents to the index' + bisect:'find by binary search the change that introduced a bug' + branch:'list, create, or delete branches' + checkout:'checkout a branch or paths to the working tree' + clone:'clone a repository into a new directory' + commit:'record changes to the repository' + diff:'show changes between commits, commit and working tree, etc' + fetch:'download objects and refs from another repository' + grep:'print lines matching a pattern' + init:'create an empty Git repository or reinitialize an existing one' + log:'show commit logs' + merge:'join two or more development histories together' + mv:'move or rename a file, a directory, or a symlink' + pull:'fetch from and merge with another repository or a local branch' + push:'update remote refs along with associated objects' + rebase:'forward-port local commits to the updated upstream head' + reset:'reset current HEAD to the specified state' + rm:'remove files from the working tree and from the index' + show:'show various types of objects' + status:'show the working tree status' + tag:'create, list, delete or verify a tag object signed with GPG') + _describe -t common-commands 'common commands' list && _ret=0 +} + +__git_zsh_cmd_alias () +{ + local -a list + list=(${${${(0)"$(git config -z --get-regexp '^alias\.')"}#alias.}%$'\n'*}) + _describe -t alias-commands 'aliases' list $* && _ret=0 +} + +__git_zsh_cmd_all () +{ + local -a list + emulate ksh -c __git_compute_all_commands + list=( ${=__git_all_commands} ) + _describe -t all-commands 'all commands' list && _ret=0 +} + +__git_zsh_main () +{ + local curcontext="$curcontext" state state_descr line + typeset -A opt_args + local -a orig_words + + orig_words=( ${words[@]} ) + + _arguments -C \ + '(-p --paginate --no-pager)'{-p,--paginate}'[pipe all output into ''less'']' \ + '(-p --paginate)--no-pager[do not pipe git output into a pager]' \ + '--git-dir=-[set the path to the repository]: :_directories' \ + '--bare[treat the repository as a bare repository]' \ + '(- :)--version[prints the git suite version]' \ + '--exec-path=-[path to where your core git programs are installed]:: :_directories' \ + '--html-path[print the path where git''s HTML documentation is installed]' \ + '--info-path[print the path where the Info files are installed]' \ + '--man-path[print the manpath (see `man(1)`) for the man pages]' \ + '--work-tree=-[set the path to the working tree]: :_directories' \ + '--namespace=-[set the git namespace]' \ + '--no-replace-objects[do not use replacement refs to replace git objects]' \ + '(- :)--help[prints the synopsis and a list of the most commonly used commands]: :->arg' \ + '(-): :->command' \ + '(-)*:: :->arg' && return + + case $state in + (command) + _alternative \ + 'alias-commands:alias:__git_zsh_cmd_alias' \ + 'common-commands:common:__git_zsh_cmd_common' \ + 'all-commands:all:__git_zsh_cmd_all' && _ret=0 + ;; + (arg) + local command="${words[1]}" __git_dir + + if (( $+opt_args[--bare] )); then + __git_dir='.' + else + __git_dir=${opt_args[--git-dir]} + fi + + (( $+opt_args[--help] )) && command='help' + + words=( ${orig_words[@]} ) + + __git_zsh_bash_func $command + ;; + esac +} + _git () { local _ret=1 - () { - emulate -L ksh - local cur cword prev - cur=${words[CURRENT-1]} - prev=${words[CURRENT-2]} - let cword=CURRENT-1 - __${service}_main - } - let _ret && _default -S '' && _ret=0 + local cur cword prev + + cur=${words[CURRENT]} + prev=${words[CURRENT-1]} + let cword=CURRENT-1 + + if (( $+functions[__${service}_zsh_main] )); then + __${service}_zsh_main + else + emulate ksh -c __${service}_main + fi + + let _ret && _default && _ret=0 return _ret } diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh index 756a951459..eaf5c369aa 100644 --- a/contrib/completion/git-prompt.sh +++ b/contrib/completion/git-prompt.sh @@ -263,14 +263,21 @@ __git_ps1 () else local r="" local b="" - if [ -f "$g/rebase-merge/interactive" ]; then - r="|REBASE-i" - b="$(cat "$g/rebase-merge/head-name")" - elif [ -d "$g/rebase-merge" ]; then - r="|REBASE-m" + local step="" + local total="" + if [ -d "$g/rebase-merge" ]; then b="$(cat "$g/rebase-merge/head-name")" + step=$(cat "$g/rebase-merge/msgnum") + total=$(cat "$g/rebase-merge/end") + if [ -f "$g/rebase-merge/interactive" ]; then + r="|REBASE-i" + else + r="|REBASE-m" + fi else if [ -d "$g/rebase-apply" ]; then + step=$(cat "$g/rebase-apply/next") + total=$(cat "$g/rebase-apply/last") if [ -f "$g/rebase-apply/rebasing" ]; then r="|REBASE" elif [ -f "$g/rebase-apply/applying" ]; then @@ -308,6 +315,10 @@ __git_ps1 () } fi + if [ -n "$step" ] && [ -n "$total" ]; then + r="$r $step/$total" + fi + local w="" local i="" local s="" @@ -340,7 +351,7 @@ __git_ps1 () [ "$(git config --bool bash.showUntrackedFiles)" != "false" ] && [ -n "$(git ls-files --others --exclude-standard)" ] then - u="%" + u="%${ZSH_VERSION+%}" fi if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then diff --git a/contrib/remote-helpers/git-remote-bzr b/contrib/remote-helpers/git-remote-bzr index bd25e08202..10300c63d1 100755 --- a/contrib/remote-helpers/git-remote-bzr +++ b/contrib/remote-helpers/git-remote-bzr @@ -13,6 +13,9 @@ # or # % git clone bzr::lp:myrepo # +# If you want to specify which branches you want track (per repo): +# git config remote-bzr.branches 'trunk, devel, test' +# import sys @@ -26,15 +29,20 @@ bzrlib.plugin.load_plugins() import bzrlib.generate_ids import bzrlib.transport import bzrlib.errors +import bzrlib.ui +import bzrlib.urlutils +import bzrlib.branch import sys import os import json import re import StringIO +import atexit, shutil, hashlib, urlparse, subprocess NAME_RE = re.compile('^([^<>]+)') AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]*)>$') +EMAIL_RE = re.compile('^([^<>]+[^ \\\t<>])?\\b(?:[ \\t<>]*?)\\b([^ \\t<>]+@[^ \\t<>]+)') RAW_AUTHOR_RE = re.compile('^(\w+) (.+)? <(.*)> (\d+) ([+-]\d+)') def die(msg, *args): @@ -47,6 +55,12 @@ def warn(msg, *args): def gittz(tz): return '%+03d%02d' % (tz / 3600, tz % 3600 / 60) +def get_config(config): + cmd = ['git', 'config', '--get', config] + process = subprocess.Popen(cmd, stdout=subprocess.PIPE) + output, _ = process.communicate() + return output + class Marks: def __init__(self, path): @@ -82,7 +96,7 @@ class Marks: 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 @@ -94,7 +108,7 @@ class Marks: return self.last_mark def is_marked(self, rev): - return self.marks.has_key(rev) + return rev in self.marks def new_mark(self, rev, mark): self.marks[rev] = mark @@ -172,9 +186,19 @@ def fixup_user(user): name = m.group(1) mail = m.group(2).strip() else: - m = NAME_RE.match(user) + m = EMAIL_RE.match(user) if m: - name = m.group(1).strip() + name = m.group(1) + mail = m.group(2) + else: + m = NAME_RE.match(user) + if m: + name = m.group(1).strip() + + if not name: + name = 'unknown' + if not mail: + mail = 'Unknown' return '%s <%s>' % (name, mail) @@ -224,7 +248,7 @@ def export_files(tree, files): else: mode = '100644' - # is the blog already exported? + # is the blob already exported? if h in filenodes: mark = filenodes[h] final.append((mode, mark, path)) @@ -248,22 +272,32 @@ def export_files(tree, files): return final -def export_branch(branch, name): - global prefix, dirname +def export_branch(repo, name): + global prefix ref = '%s/heads/%s' % (prefix, name) tip = marks.get_tip(name) + branch = bzrlib.branch.Branch.open(branches[name]) repo = branch.repository - repo.lock_read() + + branch.lock_read() revs = branch.iter_merge_sorted_revisions(None, tip, 'exclude', 'forward') - count = 0 + try: + tip_revno = branch.revision_id_to_revno(tip) + last_revno, _ = branch.last_revision_info() + total = last_revno - tip_revno + except bzrlib.errors.NoSuchRevision: + tip_revno = 0 + total = 0 - revs = [revid for revid, _, _, _ in revs if not marks.is_marked(revid)] + for revid, _, seq, _ in revs: - for revid in revs: + if marks.is_marked(revid): + continue rev = repo.get_revision(revid) + revno = seq[0] parents = rev.parent_ids time = rev.timestamp @@ -318,12 +352,18 @@ def export_branch(branch, name): print "M %s :%u %s" % f print - count += 1 - if (count % 100 == 0): - print "progress revision %s (%d/%d)" % (revid, count, len(revs)) - print "#############################################################" + if len(seq) > 1: + # let's skip branch revisions from the progress report + continue - repo.unlock() + progress = (revno - tip_revno) + if (progress % 100 == 0): + if total: + print "progress revision %d '%s' (%d/%d)" % (revno, name, progress, total) + else: + print "progress revision %d '%s' (%d)" % (revno, name, progress) + + branch.unlock() revid = branch.last_revision() @@ -335,31 +375,34 @@ def export_branch(branch, name): marks.set_tip(name, revid) def export_tag(repo, name): - global tags - print "reset refs/tags/%s" % name + global tags, prefix + + ref = '%s/tags/%s' % (prefix, name) + print "reset %s" % ref print "from :%u" % rev_to_mark(tags[name]) print def do_import(parser): global dirname - branch = parser.repo + repo = parser.repo path = os.path.join(dirname, 'marks-git') print "feature done" if os.path.exists(path): print "feature import-marks=%s" % path print "feature export-marks=%s" % path + print "feature force" sys.stdout.flush() while parser.check('import'): ref = parser[1] if ref.startswith('refs/heads/'): name = ref[len('refs/heads/'):] - export_branch(branch, name) + export_branch(repo, name) if ref.startswith('refs/tags/'): name = ref[len('refs/tags/'):] - export_tag(branch, name) + export_tag(repo, name) parser.next() print 'done' @@ -378,23 +421,21 @@ def parse_blob(parser): class CustomTree(): - def __init__(self, repo, revid, parents, files): + def __init__(self, branch, revid, parents, files): global files_cache - self.repo = repo - self.revid = revid - self.parents = parents self.updates = {} + self.branch = branch def copy_tree(revid): files = files_cache[revid] = {} - tree = repo.repository.revision_tree(revid) - repo.lock_read() + branch.lock_read() + tree = branch.repository.revision_tree(revid) try: for path, entry in tree.iter_entries_by_dir(): - files[path] = entry.file_id + files[path] = [entry.file_id, None] finally: - repo.unlock() + branch.unlock() return files if len(parents) == 0: @@ -407,12 +448,18 @@ class CustomTree(): self.base_files = copy_tree(self.base_id) self.files = files_cache[revid] = self.base_files.copy() + self.rev_files = {} + + for path, data in self.files.iteritems(): + fid, mark = data + self.rev_files[fid] = [path, mark] for path, f in files.iteritems(): - fid = self.files.get(path, None) + fid, mark = self.files.get(path, [None, None]) if not fid: fid = bzrlib.generate_ids.gen_file_id(path) f['path'] = path + self.rev_files[fid] = [path, mark] self.updates[fid] = f def last_revision(self): @@ -422,16 +469,16 @@ class CustomTree(): changes = [] def get_parent(dirname, basename): - parent_fid = self.base_files.get(dirname, None) + parent_fid, mark = self.base_files.get(dirname, [None, None]) if parent_fid: return parent_fid - parent_fid = self.files.get(dirname, None) + parent_fid, mark = self.files.get(dirname, [None, None]) if parent_fid: return parent_fid if basename == '': return None fid = bzrlib.generate_ids.gen_file_id(path) - d = add_entry(fid, dirname, 'directory') + add_entry(fid, dirname, 'directory') return fid def add_entry(fid, path, kind, mode = None): @@ -452,9 +499,8 @@ class CustomTree(): (None, basename), (None, kind), (None, executable)) - self.files[path] = change[0] + self.files[path] = [change[0], None] changes.append(change) - return change def update_entry(fid, path, kind, mode = None): dirname, basename = os.path.split(path) @@ -474,9 +520,8 @@ class CustomTree(): (None, basename), (None, kind), (None, executable)) - self.files[path] = change[0] + self.files[path] = [change[0], None] changes.append(change) - return change def remove_entry(fid, path, kind): dirname, basename = os.path.split(path) @@ -491,7 +536,6 @@ class CustomTree(): (None, None)) del self.files[path] changes.append(change) - return change for fid, f in self.updates.iteritems(): path = f['path'] @@ -505,13 +549,30 @@ class CustomTree(): else: add_entry(fid, path, 'file', f['mode']) + self.files[path][1] = f['mark'] + self.rev_files[fid][1] = f['mark'] + return changes + def get_content(self, file_id): + path, mark = self.rev_files[file_id] + if mark: + return blob_marks[mark] + + # last resort + tree = self.branch.repository.revision_tree(self.base_id) + return tree.get_file_text(file_id) + def get_file_with_stat(self, file_id, path=None): - return (StringIO.StringIO(self.updates[file_id]['data']), None) + content = self.get_content(file_id) + return (StringIO.StringIO(content), None) def get_symlink_target(self, file_id): - return self.updates[file_id]['data'] + return self.get_content(file_id) + + def id2path(self, file_id): + path, mark = self.rev_files[file_id] + return path def c_style_unescape(string): if string[0] == string[-1] == '"': @@ -519,7 +580,7 @@ def c_style_unescape(string): return string def parse_commit(parser): - global marks, blob_marks, bmarks, parsed_refs + global marks, blob_marks, parsed_refs global mode parents = [] @@ -527,8 +588,11 @@ def parse_commit(parser): ref = parser[1] parser.next() - if ref != 'refs/heads/master': - die("bzr doesn't support multiple branches; use 'master'") + if ref.startswith('refs/heads/'): + name = ref[len('refs/heads/'):] + branch = bzrlib.branch.Branch.open(branches[name]) + else: + die('unknown ref') commit_mark = parser.get_mark() parser.next() @@ -545,13 +609,17 @@ def parse_commit(parser): parents.append(parser.get_mark()) parser.next() + # fast-export adds an extra newline + if data[-1] == '\n': + data = data[:-1] + files = {} for line in parser: if parser.check('M'): t, m, mark_ref, path = line.split(' ', 3) mark = int(mark_ref[1:]) - f = { 'mode' : m, 'data' : blob_marks[mark] } + f = { 'mode' : m, 'mark' : mark } elif parser.check('D'): t, path = line.split(' ') f = { 'deleted' : True } @@ -560,20 +628,18 @@ def parse_commit(parser): path = c_style_unescape(path).decode('utf-8') files[path] = f - repo = parser.repo - committer, date, tz = committer - parents = [str(mark_to_rev(p)) for p in parents] + parents = [mark_to_rev(p) for p in parents] revid = bzrlib.generate_ids.gen_revision_id(committer, date) props = {} - props['branch-nick'] = repo.nick + props['branch-nick'] = branch.nick - mtree = CustomTree(repo, revid, parents, files) + mtree = CustomTree(branch, revid, parents, files) changes = mtree.iter_changes() - repo.lock_write() + branch.lock_write() try: - builder = repo.get_commit_builder(parents, None, date, tz, committer, props, revid) + builder = branch.get_commit_builder(parents, None, date, tz, committer, props, revid) try: list(builder.record_iter_changes(mtree, mtree.last_revision(), changes)) builder.finish_inventory() @@ -582,7 +648,7 @@ def parse_commit(parser): builder.abort() raise finally: - repo.unlock() + branch.unlock() parsed_refs[ref] = revid marks.new_mark(revid, commit_mark) @@ -593,9 +659,6 @@ def parse_reset(parser): ref = parser[1] parser.next() - if ref != 'refs/heads/master': - die("bzr doesn't support multiple branches; use 'master'") - # ugh if parser.check('commit'): parse_commit(parser) @@ -608,7 +671,7 @@ def parse_reset(parser): parsed_refs[ref] = mark_to_rev(from_mark) def do_export(parser): - global parsed_refs, dirname, peer + global parsed_refs, dirname parser.next() @@ -626,21 +689,35 @@ def do_export(parser): else: die('unhandled export command: %s' % line) - repo = parser.repo - for ref, revid in parsed_refs.iteritems(): - if ref == 'refs/heads/master': - repo.generate_revision_history(revid, marks.get_tip('master')) - revno, revid = repo.last_revision_info() - if peer: - if hasattr(peer, "import_last_revision_info_and_tags"): - peer.import_last_revision_info_and_tags(repo, revno, revid) - else: - peer.import_last_revision_info(repo.repository, revno, revid) - else: - wt = repo.bzrdir.open_workingtree() + if ref.startswith('refs/heads/'): + name = ref[len('refs/heads/'):] + branch = bzrlib.branch.Branch.open(branches[name]) + branch.generate_revision_history(revid, marks.get_tip(name)) + + if name in peers: + peer = bzrlib.branch.Branch.open(peers[name]) + try: + peer.bzrdir.push_branch(branch, revision_id=revid) + except bzrlib.errors.DivergedBranches: + print "error %s non-fast forward" % ref + continue + + try: + wt = branch.bzrdir.open_workingtree() wt.update() + except bzrlib.errors.NoWorkingTree: + pass + elif ref.startswith('refs/tags/'): + # TODO: implement tag push + print "error %s pushing tags not supported" % ref + continue + else: + # transport-helper/fast-export bugs + continue + print "ok %s" % ref + print def do_capabilities(parser): @@ -649,6 +726,7 @@ def do_capabilities(parser): print "import" print "export" print "refspec refs/heads/*:%s/heads/*" % prefix + print "refspec refs/tags/*:%s/tags/*" % prefix path = os.path.join(dirname, 'marks-git') @@ -663,9 +741,15 @@ def ref_is_valid(name): def do_list(parser): global tags - print "? refs/heads/%s" % 'master' - branch = parser.repo + master_branch = None + + for name in branches: + if not master_branch: + master_branch = name + print "? refs/heads/%s" % name + + branch = bzrlib.branch.Branch.open(branches[master_branch]) branch.lock_read() for tag, revid in branch.tags.get_tag_dict().items(): try: @@ -677,60 +761,173 @@ def do_list(parser): print "? refs/tags/%s" % tag tags[tag] = revid branch.unlock() - print "@refs/heads/%s HEAD" % 'master' + + print "@refs/heads/%s HEAD" % master_branch print +def get_remote_branch(origin, remote_branch, name): + global dirname, peers + + branch_path = os.path.join(dirname, 'clone', name) + if os.path.exists(branch_path): + # pull + d = bzrlib.bzrdir.BzrDir.open(branch_path) + branch = d.open_branch() + try: + branch.pull(remote_branch, [], None, False) + 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): + transport = repo.bzrdir.root_transport + + for fn in transport.iter_files_recursive(): + if not fn.endswith('.bzr/branch-format'): + continue + + name = subdir = fn[:-len('/.bzr/branch-format')] + 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 + def get_repo(url, alias): - global dirname, peer + global dirname, peer, branches + normal_url = bzrlib.urlutils.normalize_url(url) origin = bzrlib.bzrdir.BzrDir.open(url) - branch = origin.open_branch() + is_local = isinstance(origin.transport, bzrlib.transport.local.LocalTransport) - if not isinstance(origin.transport, bzrlib.transport.local.LocalTransport): + shared_path = os.path.join(gitdir, 'bzr') + try: + shared_dir = bzrlib.bzrdir.BzrDir.open(shared_path) + except bzrlib.errors.NotBranchError: + shared_dir = bzrlib.bzrdir.BzrDir.create(shared_path) + try: + shared_repo = shared_dir.open_repository() + except bzrlib.errors.NoRepositoryPresent: + shared_repo = shared_dir.create_repository(shared=True) + + if not is_local: clone_path = os.path.join(dirname, 'clone') - remote_branch = branch - if os.path.exists(clone_path): - # pull - d = bzrlib.bzrdir.BzrDir.open(clone_path) - branch = d.open_branch() - result = branch.pull(remote_branch, [], None, False) + if not os.path.exists(clone_path): + os.mkdir(clone_path) + else: + # check and remove old organization + try: + bdir = bzrlib.bzrdir.BzrDir.open(clone_path) + bdir.destroy_repository() + except bzrlib.errors.NotBranchError: + pass + 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: - # clone - d = origin.sprout(clone_path, None, - hardlink=True, create_tree_if_local=False, - source_branch=remote_branch) - branch = d.open_branch() - branch.bind(remote_branch) - - peer = remote_branch + branch = remote_branch + + branches[name] = branch.base + + return branch.repository else: - peer = None + # repository - return branch + 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): + + 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 + + return repo + +def fix_path(alias, orig_url): + url = urlparse.urlparse(orig_url, 'file') + if url.scheme != 'file' or os.path.isabs(url.path): + return + abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url) + cmd = ['git', 'config', 'remote.%s.url' % alias, "bzr::%s" % abs_url] + subprocess.call(cmd) def main(args): - global marks, prefix, dirname + global marks, prefix, gitdir, dirname global tags, filenodes global blob_marks global parsed_refs global files_cache + global is_tmp + global branches, peers alias = args[1] url = args[2] - prefix = 'refs/bzr/%s' % alias tags = {} filenodes = {} blob_marks = {} parsed_refs = {} files_cache = {} + marks = None + branches = {} + peers = {} + + if alias[5:] == url: + is_tmp = True + alias = hashlib.sha1(alias).hexdigest() + else: + is_tmp = False + prefix = 'refs/bzr/%s' % alias gitdir = os.environ['GIT_DIR'] dirname = os.path.join(gitdir, 'bzr', alias) + if not is_tmp: + fix_path(alias, url) + if not os.path.exists(dirname): os.makedirs(dirname) + if hasattr(bzrlib.ui.ui_factory, 'be_quiet'): + bzrlib.ui.ui_factory.be_quiet(True) + repo = get_repo(url, alias) marks_path = os.path.join(dirname, 'marks-int') @@ -750,6 +947,13 @@ def main(args): die('unhandled command: %s' % line) sys.stdout.flush() - marks.store() +def bye(): + if not marks: + return + if not is_tmp: + marks.store() + else: + shutil.rmtree(dirname) +atexit.register(bye) sys.exit(main(sys.argv)) diff --git a/contrib/remote-helpers/git-remote-hg b/contrib/remote-helpers/git-remote-hg index 328c2dc76d..1dd3d7030e 100755 --- a/contrib/remote-helpers/git-remote-hg +++ b/contrib/remote-helpers/git-remote-hg @@ -8,8 +8,11 @@ # Just copy to your ~/bin, or anywhere in your $PATH. # Then you can clone with: # git clone hg::/path/to/mercurial/repo/ +# +# For remote repositories a local clone is stored in +# "$GIT_DIR/hg/origin/clone/.hg/". -from mercurial import hg, ui, bookmarks, context, util, encoding +from mercurial import hg, ui, bookmarks, context, encoding, node, error, extensions import re import sys @@ -18,8 +21,20 @@ import json import shutil import subprocess import urllib +import atexit +import urlparse, hashlib # +# If you are not in hg-git-compat mode and want to disable the tracking of +# 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 +# # If you want to switch to hg-git compatibility mode: # git config --global remote-hg.hg-git-compat true # @@ -36,6 +51,7 @@ import urllib NAME_RE = re.compile('^([^<>]+)') AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]*)>$') +EMAIL_RE = re.compile('^([^<>]+[^ \\\t<>])?\\b(?:[ \\t<>]*?)\\b([^ \\t<>]+@[^ \\t<>]+)') AUTHOR_HG_RE = re.compile('^(.*?) ?<(.*?)(?:>(.+)?)?$') RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)') @@ -56,12 +72,30 @@ def hgmode(mode): m = { '100755': 'x', '120000': 'l' } return m.get(mode, '') +def hghex(node): + return hg.node.hex(node) + +def hgref(ref): + return ref.replace('___', ' ') + +def gitref(ref): + return ref.replace(' ', '___') + def get_config(config): cmd = ['git', 'config', '--get', config] process = subprocess.Popen(cmd, stdout=subprocess.PIPE) output, _ = process.communicate() return output +def get_config_bool(config, default=False): + value = get_config(config).rstrip('\n') + if value == "true": + return True + elif value == "false": + return False + else: + return default + class Marks: def __init__(self, path): @@ -101,6 +135,10 @@ class Marks: def to_rev(self, mark): return self.rev_marks[mark] + def next_mark(self): + self.last_mark += 1 + return self.last_mark + def get_mark(self, rev): self.last_mark += 1 self.marks[str(rev)] = self.last_mark @@ -112,7 +150,7 @@ class Marks: self.last_mark = mark def is_marked(self, rev): - return self.marks.has_key(str(rev)) + return str(rev) in self.marks def get_tip(self, branch): return self.tips.get(branch, 0) @@ -188,19 +226,43 @@ class Parser: tz = ((tz / 100) * 3600) + ((tz % 100) * 60) return (user, int(date), -tz) -def export_file(fc): - d = fc.data() - print "M %s inline %s" % (gitmode(fc.flags()), fc.path()) - print "data %d" % len(d) - print d +def fix_file_path(path): + if not os.path.isabs(path): + return path + return os.path.relpath(path, '/') + +def export_files(files): + global marks, filenodes + + final = [] + for f in files: + fid = node.hex(f.filenode()) + + if fid in filenodes: + mark = filenodes[fid] + else: + mark = marks.next_mark() + filenodes[fid] = mark + d = f.data() + + print "blob" + print "mark :%u" % mark + print "data %d" % len(d) + print d + + path = fix_file_path(f.path()) + final.append((gitmode(f.flags()), mark, path)) + + return final def get_filechanges(repo, ctx, parent): modified = set() added = set() removed = set() - cur = ctx.manifest() + # load earliest manifest first for caching reasons prev = repo[parent].manifest().copy() + cur = ctx.manifest() for fn in cur: if fn in prev: @@ -221,9 +283,14 @@ def fixup_user_git(user): name = m.group(1) mail = m.group(2).strip() else: - m = NAME_RE.match(user) + m = EMAIL_RE.match(user) if m: - name = m.group(1).strip() + name = m.group(1) + mail = m.group(2) + else: + m = NAME_RE.match(user) + if m: + name = m.group(1).strip() return (name, mail) def fixup_user_hg(user): @@ -267,17 +334,33 @@ def get_repo(url, alias): myui = ui.ui() myui.setconfig('ui', 'interactive', 'off') + myui.fout = sys.stderr + + 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 if hg.islocal(url): repo = hg.repository(myui, url) else: local_path = os.path.join(dirname, 'clone') if not os.path.exists(local_path): - peer, dstpeer = hg.clone(myui, {}, url, local_path, update=False, pull=True) + 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) - peer = hg.peer(myui, {}, url) + try: + peer = hg.peer(myui, {}, url) + except: + die('Repository error') repo.pull(peer, heads=None, force=True) return repo @@ -296,10 +379,6 @@ def export_ref(repo, name, kind, head): ename = '%s/%s' % (kind, name) tip = marks.get_tip(ename) - # mercurial takes too much time checking this - if tip and tip == head.rev(): - # nothing to do - return revs = xrange(tip, head.rev() + 1) count = 0 @@ -326,6 +405,8 @@ def export_ref(repo, name, kind, head): else: modified, removed = get_filechanges(repo, c, parents[0]) + desc += '\n' + if mode == 'hg': extra_msg = '' @@ -349,13 +430,14 @@ def export_ref(repo, name, kind, head): else: extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value)) - desc += '\n' if extra_msg: desc += '\n--HG--\n' + extra_msg if len(parents) == 0 and rev: print 'reset %s/%s' % (prefix, ename) + 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 "author %s" % (author) @@ -368,16 +450,15 @@ def export_ref(repo, name, kind, head): if len(parents) > 1: print "merge :%s" % (rev_to_mark(parents[1])) - for f in modified: - export_file(c.filectx(f)) + for f in modified_final: + print "M %s :%u %s" % f for f in removed: - print "D %s" % (f) + print "D %s" % (fix_file_path(f)) print count += 1 if (count % 100 == 0): print "progress revision %d '%s' (%d/%d)" % (rev, name, count, len(revs)) - print "#############################################################" # make sure the ref is updated print "reset %s/%s" % (prefix, ename) @@ -387,10 +468,10 @@ def export_ref(repo, name, kind, head): marks.set_tip(ename, rev) def export_tag(repo, tag): - export_ref(repo, tag, 'tags', repo[tag]) + export_ref(repo, tag, 'tags', repo[hgref(tag)]) def export_bookmark(repo, bmark): - head = bmarks[bmark] + head = bmarks[hgref(bmark)] export_ref(repo, bmark, 'bookmarks', head) def export_branch(repo, branch): @@ -419,19 +500,24 @@ def do_capabilities(parser): 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 get_branch_tip(repo, branch): global branches - heads = branches.get(branch, None) + heads = branches.get(hgref(branch), None) if not heads: return None # verify there's only one head if (len(heads) > 1): warn("Branch '%s' has more than one head, consider merging" % branch) - # older versions of mercurial don't have this - if hasattr(repo, "branchtip"): - return repo.branchtip(branch) + return branch_tip(repo, hgref(branch)) return heads[0] @@ -453,11 +539,12 @@ def list_head(repo, cur): head = 'master' bmarks[head] = node + head = gitref(head) print "@refs/heads/%s HEAD" % head g_head = (head, node) def do_list(parser): - global branches, bmarks, mode, track_branches + global branches, bmarks, track_branches repo = parser.repo for bmark, node in bookmarks.listbookmarks(repo).iteritems(): @@ -474,15 +561,15 @@ def do_list(parser): branches[branch] = heads for branch in branches: - print "? refs/heads/branches/%s" % branch + print "? refs/heads/branches/%s" % gitref(branch) for bmark in bmarks: - print "? refs/heads/%s" % bmark + print "? refs/heads/%s" % gitref(bmark) for tag, node in repo.tagslist(): if tag == 'tip': continue - print "? refs/tags/%s" % tag + print "? refs/tags/%s" % gitref(tag) print @@ -531,7 +618,6 @@ def parse_blob(parser): data = parser.get_data() blob_marks[mark] = data parser.next() - return def get_merge_files(repo, p1, p2, files): for e in repo[p1].files(): @@ -542,7 +628,7 @@ def get_merge_files(repo, p1, p2, files): files[e] = f def parse_commit(parser): - global marks, blob_marks, bmarks, parsed_refs + global marks, blob_marks, parsed_refs global mode from_mark = merge_mark = None @@ -567,6 +653,10 @@ def parse_commit(parser): if parser.check('merge'): die('octopus merges are not supported yet') + # fast-export adds an extra newline + if data[-1] == '\n': + data = data[:-1] + files = {} for line in parser: @@ -575,7 +665,7 @@ def parse_commit(parser): mark = int(mark_ref[1:]) f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] } elif parser.check('D'): - t, path = line.split(' ') + t, path = line.split(' ', 1) f = { 'deleted' : True } else: die('Unknown file command: %s' % line) @@ -618,11 +708,16 @@ def parse_commit(parser): if merge_mark: get_merge_files(repo, p1, p2, files) + # Check if the ref is supposed to be a named branch + if ref.startswith('refs/heads/branches/'): + branch = ref[len('refs/heads/branches/'):] + extra['branch'] = hgref(branch) + if mode == 'hg': i = data.find('\n--HG--\n') if i >= 0: tmp = data[i + len('\n--HG--\n'):].strip() - for k, v in [e.split(' : ') for e in tmp.split('\n')]: + for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]: if k == 'rename': old, new = v.split(' => ', 1) files[new]['rename'] = old @@ -647,10 +742,11 @@ def parse_commit(parser): rev = repo[node].rev() parsed_refs[ref] = node - marks.new_mark(rev, commit_mark) def parse_reset(parser): + global parsed_refs + ref = parser[1] parser.next() # ugh @@ -675,11 +771,46 @@ def parse_tag(parser): data = parser.get_data() parser.next() - # nothing to do + parsed_tags[name] = (tagger, data) + +def write_tag(repo, tag, node, msg, author): + branch = repo[node].branch() + tip = branch_tip(repo, branch) + tip = repo[tip] + + def getfilectx(repo, memctx, f): + try: + fctx = tip.filectx(f) + data = fctx.data() + except error.ManifestLookupError: + data = "" + content = data + "%s %s\n" % (hghex(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 + + ctx = context.memctx(repo, (p1, p2), msg, + ['.hgtags'], getfilectx, + user, (date, tz), {'branch' : branch}) + + tmp = encoding.encoding + encoding.encoding = 'utf-8' + + tagnode = repo.commitctx(ctx) + + encoding.encoding = tmp + + return tagnode def do_export(parser): global parsed_refs, bmarks, peer + p_bmarks = [] + parser.next() for line in parser.each_block('done'): @@ -698,56 +829,93 @@ def do_export(parser): for ref, node in parsed_refs.iteritems(): if ref.startswith('refs/heads/branches'): - pass + branch = ref[len('refs/heads/branches/'):] + if branch in branches and node in branches[branch]: + # up to date + continue + print "ok %s" % ref elif ref.startswith('refs/heads/'): bmark = ref[len('refs/heads/'):] - if bmark in bmarks: - old = bmarks[bmark].hex() - else: - old = '' - if not bookmarks.pushbookmark(parser.repo, bmark, old, node): - continue + p_bmarks.append((bmark, node)) + continue elif ref.startswith('refs/tags/'): tag = ref[len('refs/tags/'):] - parser.repo.tag([tag], node, None, True, None, {}) + 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) + else: + fp = parser.repo.opener('localtags', 'a') + fp.write('%s %s\n' % (hghex(node), tag)) + fp.close() + 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 old == new: + continue + + 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: + rb = peer.listkeys('bookmarks') + old = rb.get(bmark, '') + if not peer.pushkey('bookmarks', bmark, old, new): + print "error %s" % ref + continue + print "ok %s" % ref print - if peer: - parser.repo.push(peer, force=False) - def fix_path(alias, repo, orig_url): - repo_url = util.url(repo.url()) - url = util.url(orig_url) - if str(url) == str(repo_url): + url = urlparse.urlparse(orig_url, 'file') + if url.scheme != 'file' or os.path.isabs(url.path): return - cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % repo_url] + abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url) + cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url] subprocess.call(cmd) def main(args): global prefix, dirname, branches, bmarks global marks, blob_marks, parsed_refs global peer, mode, bad_mail, bad_name - global track_branches + global track_branches, force_push, is_tmp + global parsed_tags + global filenodes alias = args[1] url = args[2] peer = None - hg_git_compat = False - track_branches = True - try: - if get_config('remote-hg.hg-git-compat') == 'true\n': - hg_git_compat = True - track_branches = False - if get_config('remote-hg.track-branches') == 'false\n': - track_branches = False - except subprocess.CalledProcessError: - pass + 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') if hg_git_compat: mode = 'hg' @@ -760,7 +928,7 @@ def main(args): if alias[4:] == url: is_tmp = True - alias = util.sha1(alias).hexdigest() + alias = hashlib.sha1(alias).hexdigest() else: is_tmp = False @@ -770,6 +938,9 @@ def main(args): bmarks = {} blob_marks = {} parsed_refs = {} + marks = None + parsed_tags = {} + filenodes = {} repo = get_repo(url, alias) prefix = 'refs/hg/%s' % alias @@ -783,6 +954,10 @@ def main(args): marks_path = os.path.join(dirname, 'marks-hg') marks = Marks(marks_path) + if sys.platform == 'win32': + import msvcrt + msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) + parser = Parser(repo) for line in parser: if parser.check('capabilities'): @@ -797,9 +972,13 @@ def main(args): die('unhandled command: %s' % line) sys.stdout.flush() +def bye(): + if not marks: + return if not is_tmp: marks.store() else: shutil.rmtree(dirname) +atexit.register(bye) sys.exit(main(sys.argv)) diff --git a/contrib/remote-helpers/test-bzr.sh b/contrib/remote-helpers/test-bzr.sh index 34666e1d0f..5dfa070b64 100755 --- a/contrib/remote-helpers/test-bzr.sh +++ b/contrib/remote-helpers/test-bzr.sh @@ -228,4 +228,114 @@ test_expect_success 'push utf-8 filenames' ' 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 +' + +cat > expected <<EOF +origin/HEAD +origin/branch +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_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" && + + ( + bzr init bzrrepo && + cd bzrrepo && + + echo one >> content && + bzr add content && + bzr commit -m one && + + echo two >> content && + bzr commit -m two + ) && + + git clone "bzr::$PWD/bzrrepo" gitrepo && + + ( + cd bzrrepo && + bzr uncommit --force && + + 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 + ) && + + (cd gitrepo && + git fetch && + git log --format="%an %ad %s" --date=short origin/master > ../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 1d61982436..f569697734 100755 --- a/contrib/remote-helpers/test-hg-bidi.sh +++ b/contrib/remote-helpers/test-hg-bidi.sh @@ -22,7 +22,6 @@ fi # clone to a git repo git_clone () { - hg -R $1 bookmark -f -r tip master && git clone -q "hg::$PWD/$1" $2 } @@ -30,6 +29,7 @@ git_clone () { 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/*' ) && @@ -50,7 +50,8 @@ hg_push () { } hg_log () { - hg -R $1 log --graph --debug | grep -v 'tag: *default/' + hg -R $1 log --graph --debug >log && + grep -v 'tag: *default/' log } setup () { @@ -62,13 +63,15 @@ setup () { echo "commit = -d \"0 0\"" echo "debugrawcommit = -d \"0 0\"" echo "tag = -d \"0 0\"" + echo "[extensions]" + echo "graphlog =" ) >> "$HOME"/.hgrc && git config --global remote-hg.hg-git-compat true - export HGEDITOR=/usr/bin/true - - export GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0230" - export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" + HGEDITOR=/usr/bin/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 } setup @@ -85,7 +88,8 @@ test_expect_success 'encoding' ' git add alpha && git commit -m "add älphà" && - export GIT_AUTHOR_NAME="tést èncödîng" && + GIT_AUTHOR_NAME="tést èncödîng" && + export GIT_AUTHOR_NAME && echo beta > beta && git add beta && git commit -m "add beta" && @@ -200,8 +204,8 @@ test_expect_success 'hg branch' ' hg_push hgrepo gitrepo && hg_clone gitrepo hgrepo2 && - : TODO, avoid "master" bookmark && - (cd hgrepo2 && hg checkout gamma) && + : Back to the common revision && + (cd hgrepo && hg checkout default) && hg_log hgrepo > expected && hg_log hgrepo2 > actual && diff --git a/contrib/remote-helpers/test-hg-hg-git.sh b/contrib/remote-helpers/test-hg-hg-git.sh index 3f253b7de7..7f579c8436 100755 --- a/contrib/remote-helpers/test-hg-hg-git.sh +++ b/contrib/remote-helpers/test-hg-hg-git.sh @@ -27,7 +27,6 @@ fi # clone to a git repo with git git_clone_git () { - hg -R $1 bookmark -f -r tip master && git clone -q "hg::$PWD/$1" $2 } @@ -35,6 +34,7 @@ git_clone_git () { 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/*' ) && @@ -47,7 +47,7 @@ git_clone_hg () { ( git init -q $2 && cd $1 && - hg bookmark -f -r tip master && + hg bookmark -i -f -r tip master && hg -q push -r master ../$2 || true ) } @@ -78,7 +78,8 @@ hg_push_hg () { } hg_log () { - hg -R $1 log --graph --debug | grep -v 'tag: *default/' + hg -R $1 log --graph --debug >log && + grep -v 'tag: *default/' log } git_log () { @@ -97,14 +98,17 @@ setup () { echo "[extensions]" echo "hgext.bookmarks =" echo "hggit =" + echo "graphlog =" ) >> "$HOME"/.hgrc && git config --global receive.denycurrentbranch warn git config --global remote-hg.hg-git-compat true + git config --global remote-hg.track-branches false - export HGEDITOR=/usr/bin/true + HGEDITOR=/usr/bin/true - export GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0230" - export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" + GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0230" + GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" + export HGEDITOR GIT_AUTHOR_DATE GIT_COMMITTER_DATE } setup @@ -294,7 +298,8 @@ test_expect_success 'encoding' ' git add alpha && git commit -m "add älphà" && - export GIT_AUTHOR_NAME="tést èncödîng" && + GIT_AUTHOR_NAME="tést èncödîng" && + export GIT_AUTHOR_NAME && echo beta > beta && git add beta && git commit -m "add beta" && @@ -451,8 +456,6 @@ test_expect_success 'hg author' ' git_log gitrepo-$x > git-log-$x done && - test_cmp git-log-hg git-log-git && - test_cmp hg-log-hg hg-log-git && test_cmp git-log-hg git-log-git ' diff --git a/contrib/remote-helpers/test-hg.sh b/contrib/remote-helpers/test-hg.sh index 7bb81f2f8e..8de2aa7fec 100755 --- a/contrib/remote-helpers/test-hg.sh +++ b/contrib/remote-helpers/test-hg.sh @@ -118,4 +118,40 @@ test_expect_success 'update bookmark' ' hg -R hgrepo bookmarks | egrep "devel[ ]+3:" ' +author_test () { + echo $1 >> content && + hg commit -u "$2" -m "add $1" && + echo "$3" >> ../expected +} + +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_done |