Merge branch 'fc/remote-hg'
authorJunio C Hamano <gitster@pobox.com>
Mon, 22 Apr 2013 01:39:58 +0000 (18:39 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 22 Apr 2013 01:39:58 +0000 (18:39 -0700)
Updates remote-hg helper (in contrib/).

* fc/remote-hg: (21 commits)
remote-hg: activate graphlog extension for hg_log()
remote-hg: fix bad file paths
remote-hg: document location of stored hg repository
remote-hg: fix bad state issue
remote-hg: add 'insecure' option
remote-hg: add simple mail test
remote-hg: add basic author tests
remote-hg: show more proper errors
remote-hg: force remote push
remote-hg: push to the appropriate branch
remote-hg: update tags globally
remote-hg: update remote bookmarks
remote-hg: refactor export
remote-hg: split bookmark handling
remote-hg: redirect buggy mercurial output
remote-hg: trivial test cleanups
remote-hg: make sure fake bookmarks are updated
remote-hg: fix for files with spaces
remote-hg: properly report errors on bookmark pushes
remote-hg: add missing config variable in doc
...

1  2 
contrib/remote-helpers/git-remote-hg
contrib/remote-helpers/test-hg-hg-git.sh
contrib/remote-helpers/test-hg.sh
index 45f6c80d45ab9848d1197e34a00b8ac5977a05fc,a5f0013c627969c1741001c3b25bc450ad073a51..548133121d23a328d76d68d058fd4fb14b60fb16
@@@ -8,8 -8,11 +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, util, encoding, node, error
  
  import re
  import sys
@@@ -18,11 -21,22 +21,22 @@@ import jso
  import shutil
  import subprocess
  import urllib
+ import atexit
  
  #
  # If you want to switch to hg-git compatibility mode:
  # git config --global remote-hg.hg-git-compat true
  #
+ # 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 remote-hg.insecure true
+ #
  # git:
  # Sensible defaults for git.
  # hg bookmarks are exported as git branches, hg branches are prefixed
@@@ -56,6 -70,9 +70,9 @@@ def hgmode(mode)
      m = { '100755': 'x', '120000': 'l' }
      return m.get(mode, '')
  
+ def hghex(node):
+     return hg.node.hex(node)
  def get_config(config):
      cmd = ['git', 'config', '--get', config]
      process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
@@@ -188,9 -205,15 +205,15 @@@ class Parser
          tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
          return (user, int(date), -tz)
  
+ def fix_file_path(path):
+     if not os.path.isabs(path):
+         return path
+     return os.path.relpath(path, '/')
  def export_file(fc):
      d = fc.data()
-     print "M %s inline %s" % (gitmode(fc.flags()), fc.path())
+     path = fix_file_path(fc.path())
+     print "M %s inline %s" % (gitmode(fc.flags()), path)
      print "data %d" % len(d)
      print d
  
@@@ -267,17 -290,30 +290,30 @@@ def get_repo(url, alias)
  
      myui = ui.ui()
      myui.setconfig('ui', 'interactive', 'off')
+     myui.fout = sys.stderr
+     try:
+         if get_config('remote-hg.insecure') == 'true\n':
+             myui.setconfig('web', 'cacerts', '')
+     except subprocess.CalledProcessError:
+         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
@@@ -326,8 -362,6 +362,8 @@@ def export_ref(repo, name, kind, head)
          else:
              modified, removed = get_filechanges(repo, c, parents[0])
  
 +        desc += '\n'
 +
          if mode == 'hg':
              extra_msg = ''
  
                  else:
                      extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
  
 -            desc += '\n'
              if extra_msg:
                  desc += '\n--HG--\n' + extra_msg
  
          for f in modified:
              export_file(c.filectx(f))
          for f in removed:
-             print "D %s" % (f)
+             print "D %s" % (fix_file_path(f))
          print
  
          count += 1
@@@ -532,7 -567,6 +568,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():
              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
              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)
      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/'):
+         extra['branch'] = ref[len('refs/heads/branches/'):]
      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
      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
@@@ -681,6 -720,8 +721,8 @@@ def parse_tag(parser)
  def do_export(parser):
      global parsed_refs, bmarks, peer
  
+     p_bmarks = []
      parser.next()
  
      for line in parser.each_block('done'):
  
      for ref, node in parsed_refs.iteritems():
          if ref.startswith('refs/heads/branches'):
-             pass
+             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, {})
+             if mode == 'git':
+                 msg = 'Added tag %s for changeset %s' % (tag, hghex(node[:6]));
+                 parser.repo.tag([tag], node, msg, False, None, {})
+             else:
+                 parser.repo.tag([tag], node, None, True, None, {})
+             print "ok %s" % ref
          else:
              # transport-helper/fast-export bugs
              continue
+     if peer:
+         parser.repo.push(peer, force=force_push)
+     # 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 bmark == 'master' and 'master' not in parser.repo._bookmarks:
+             # fake bookmark
+             pass
+         elif bookmarks.pushbookmark(parser.repo, bmark, old, new):
+             # updated locally
+             pass
+         else:
+             print "error %s" % ref
+             continue
+         if peer:
+             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)
@@@ -733,7 -801,7 +802,7 @@@ 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
  
      alias = args[1]
      url = args[2]
  
      hg_git_compat = False
      track_branches = True
+     force_push = 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
+         if get_config('remote-hg.force-push') == 'false\n':
+             force_push = False
      except subprocess.CalledProcessError:
          pass
  
      bmarks = {}
      blob_marks = {}
      parsed_refs = {}
+     marks = None
  
      repo = get_repo(url, alias)
      prefix = 'refs/hg/%s' % alias
              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))
index 3f253b7de75a6fa44c4cff3b2e1f53f9feffcf5d,5daad6b961faadb72eecabf5cd196ac3f0f56c96..253e65aaa8881581b971eb51d518a4ad393f4b3d
@@@ -27,7 -27,6 +27,6 @@@ f
  
  # 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 +34,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/*'
        ) &&
@@@ -47,7 -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 +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,6 -98,7 +98,7 @@@ 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
@@@ -140,6 -142,7 +142,6 @@@ test_expect_success 'executable bit' 
                git_clone_$x hgrepo-$x gitrepo2-$x &&
                git_log gitrepo2-$x > log-$x
        done &&
 -      cp -r log-* output-* /tmp/foo/ &&
  
        test_cmp output-hg output-git &&
        test_cmp log-hg log-git
index 7bb81f2f8e0e288eb7e392075491fc1df0bd91d1,6a1e4b1ad629c99e6c98786810c8601181cddd6d..d5b873051f5d79e277fb8ed6cfcd0db85f0d429f
@@@ -115,7 -115,43 +115,43 @@@ test_expect_success 'update bookmark' 
    git push
    ) &&
  
 -  hg -R hgrepo bookmarks | grep "devel\s\+3:"
 +  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 <unknown>" &&
+   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 <unknown>" &&
+   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 <unknown>" &&
+   author_test iota "test < test <at> example <dot> com>" "test <unknown>" &&
+   author_test kappa "test@example.com" "test@example.com <unknown>"
+   ) &&
+   git clone "hg::$PWD/hgrepo" gitrepo &&
+   git --git-dir=gitrepo/.git log --reverse --format="%an <%ae>" > actual &&
+   test_cmp expected actual
+ '
  test_done