contrib/hgk.py
changeset 1278 6a0d373d3126
parent 1243 9d10f89b75a5
child 1308 2073e5a71008
equal deleted inserted replaced
1277:0bf11906df28 1278:6a0d373d3126
       
     1 #!/usr/bin/env python
       
     2 #
       
     3 # Minimal support for git commands on an hg repository
       
     4 #
       
     5 # Copyright 2005 Chris Mason <mason@suse.com>
       
     6 #
       
     7 # This software may be used and distributed according to the terms
       
     8 # of the GNU General Public License, incorporated herein by reference.
       
     9 
       
    10 import time, sys, signal, os
       
    11 from mercurial import hg, mdiff, fancyopts, commands, ui, util
       
    12 
       
    13 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
       
    14            changes=None, text=False):
       
    15     def date(c):
       
    16         return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
       
    17 
       
    18     if not changes:
       
    19         (c, a, d, u) = repo.changes(node1, node2, files, match=match)
       
    20     else:
       
    21         (c, a, d, u) = changes
       
    22     if files:
       
    23         c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
       
    24 
       
    25     if not c and not a and not d:
       
    26         return
       
    27 
       
    28     if node2:
       
    29         change = repo.changelog.read(node2)
       
    30         mmap2 = repo.manifest.read(change[0])
       
    31         date2 = date(change)
       
    32         def read(f):
       
    33             return repo.file(f).read(mmap2[f])
       
    34     else:
       
    35         date2 = time.asctime()
       
    36         if not node1:
       
    37             node1 = repo.dirstate.parents()[0]
       
    38         def read(f):
       
    39             return repo.wfile(f).read()
       
    40 
       
    41     change = repo.changelog.read(node1)
       
    42     mmap = repo.manifest.read(change[0])
       
    43     date1 = date(change)
       
    44 
       
    45     for f in c:
       
    46         to = None
       
    47         if f in mmap:
       
    48             to = repo.file(f).read(mmap[f])
       
    49         tn = read(f)
       
    50         fp.write("diff --git a/%s b/%s\n" % (f, f))
       
    51         fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
       
    52     for f in a:
       
    53         to = None
       
    54         tn = read(f)
       
    55         fp.write("diff --git /dev/null b/%s\n" % (f))
       
    56         fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
       
    57     for f in d:
       
    58         to = repo.file(f).read(mmap[f])
       
    59         tn = None
       
    60         fp.write("diff --git a/%s /dev/null\n" % (f))
       
    61         fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text))
       
    62 
       
    63 def difftree(ui, repo, node1=None, node2=None, **opts):
       
    64     """diff trees from two commits"""
       
    65     def __difftree(repo, node1, node2):
       
    66         def date(c):
       
    67             return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
       
    68 
       
    69         if node2:
       
    70             change = repo.changelog.read(node2)
       
    71             mmap2 = repo.manifest.read(change[0])
       
    72             (c, a, d, u) = repo.changes(node1, node2)
       
    73             def read(f): return repo.file(f).read(mmap2[f])
       
    74             date2 = date(change)
       
    75         else:
       
    76             date2 = time.asctime()
       
    77             (c, a, d, u) = repo.changes(node1, None)
       
    78             if not node1:
       
    79                 node1 = repo.dirstate.parents()[0]
       
    80             def read(f): return file(os.path.join(repo.root, f)).read()
       
    81 
       
    82         change = repo.changelog.read(node1)
       
    83         mmap = repo.manifest.read(change[0])
       
    84         date1 = date(change)
       
    85         empty = "0" * 40;
       
    86 
       
    87         for f in c:
       
    88             # TODO get file permissions
       
    89             print ":100664 100664 %s %s M\t%s\t%s" % (hg.hex(mmap[f]), 
       
    90                                                       hg.hex(mmap2[f]), f, f)
       
    91         for f in a:
       
    92             print ":000000 100664 %s %s N\t%s\t%s" % (empty, hg.hex(mmap2[f]), f, f)
       
    93         for f in d:
       
    94             print ":100664 000000 %s %s D\t%s\t%s" % (hg.hex(mmap[f]), empty, f, f)
       
    95     ##
       
    96 
       
    97     while True:
       
    98         if opts['stdin']:
       
    99             try:
       
   100                 line = raw_input().split(' ')
       
   101                 node1 = line[0]
       
   102                 if len(line) > 1:
       
   103                     node2 = line[1]
       
   104                 else:
       
   105                     node2 = None
       
   106             except EOFError:
       
   107                 break
       
   108         node1 = repo.lookup(node1)
       
   109         if node2:
       
   110             node2 = repo.lookup(node2)
       
   111         else:
       
   112             node2 = node1
       
   113             node1 = repo.changelog.parents(node1)[0]
       
   114         if opts['patch']:
       
   115             if opts['pretty']:
       
   116                 catcommit(repo, node2, "")
       
   117             dodiff(sys.stdout, ui, repo, node1, node2)
       
   118         else:
       
   119             __difftree(repo, node1, node2)
       
   120         if not opts['stdin']:
       
   121             break
       
   122 
       
   123 def catcommit(repo, n, prefix, changes=None):
       
   124     nlprefix = '\n' + prefix;
       
   125     (p1, p2) = repo.changelog.parents(n)
       
   126     (h, h1, h2) = map(hg.hex, (n, p1, p2))
       
   127     (i1, i2) = map(repo.changelog.rev, (p1, p2))
       
   128     if not changes:
       
   129         changes = repo.changelog.read(n)
       
   130     print "tree %s" % (hg.hex(changes[0]))
       
   131     if i1 != -1: print "parent %s" % (h1)
       
   132     if i2 != -1: print "parent %s" % (h2)
       
   133     date_ar = changes[2].split(' ')
       
   134     date = int(float(date_ar[0]))
       
   135     lines = changes[4].splitlines()
       
   136     if lines[-1].startswith('committer:'):
       
   137         committer = lines[-1].split(': ')[1].rstrip()
       
   138     else:
       
   139         committer = "%s %s %s" % (changes[1], date, date_ar[1])
       
   140         
       
   141     print "author %s %s %s" % (changes[1], date, date_ar[1])
       
   142     print "committer %s" % (committer)
       
   143     print ""
       
   144     if prefix != "":
       
   145         print "%s%s" % (prefix, changes[4].replace('\n', nlprefix).strip())
       
   146     else:
       
   147         print changes[4]
       
   148     if prefix:
       
   149         sys.stdout.write('\0')
       
   150 
       
   151 def base(ui, repo, node1, node2):
       
   152     """Output common ancestor information"""
       
   153     node1 = repo.lookup(node1)
       
   154     node2 = repo.lookup(node2)
       
   155     n = repo.changelog.ancestor(node1, node2)
       
   156     print hg.hex(n)
       
   157 
       
   158 def catfile(ui, repo, type=None, r=None, **opts):
       
   159     """cat a specific revision"""
       
   160     # in stdin mode, every line except the commit is prefixed with two
       
   161     # spaces.  This way the our caller can find the commit without magic
       
   162     # strings
       
   163     #
       
   164     prefix = ""
       
   165     if opts['stdin']:
       
   166         try:
       
   167             (type, r) = raw_input().split(' ');
       
   168             prefix = "    "
       
   169         except EOFError:
       
   170             return
       
   171 
       
   172     else:
       
   173         if not type or not r:
       
   174             ui.warn("cat-file: type or revision not supplied\n")
       
   175             commands.help_(ui, 'cat-file')
       
   176 
       
   177     while r:
       
   178         if type != "commit":
       
   179             sys.stderr.write("aborting hg cat-file only understands commits\n")
       
   180             sys.exit(1);
       
   181         n = repo.lookup(r)
       
   182         catcommit(repo, n, prefix)
       
   183         if opts['stdin']:
       
   184             try:
       
   185                 (type, r) = raw_input().split(' ');
       
   186             except EOFError:
       
   187                 break
       
   188         else:
       
   189             break
       
   190 
       
   191 # git rev-tree is a confusing thing.  You can supply a number of
       
   192 # commit sha1s on the command line, and it walks the commit history
       
   193 # telling you which commits are reachable from the supplied ones via
       
   194 # a bitmask based on arg position.
       
   195 # you can specify a commit to stop at by starting the sha1 with ^
       
   196 def revtree(args, repo, full="tree", maxnr=0, parents=False):
       
   197     def chlogwalk():
       
   198         ch = repo.changelog
       
   199         count = ch.count()
       
   200         i = count
       
   201         l = [0] * 100
       
   202         chunk = 100
       
   203         while True:
       
   204             if chunk > i:
       
   205                 chunk = i
       
   206                 i = 0
       
   207             else:
       
   208                 i -= chunk
       
   209 
       
   210             for x in xrange(0, chunk):
       
   211                 if i + x >= count:
       
   212                     l[chunk - x:] = [0] * (chunk - x)
       
   213                     break
       
   214                 if full != None:
       
   215                     l[x] = ch.read(ch.node(i + x))
       
   216                 else:
       
   217                     l[x] = 1
       
   218             for x in xrange(chunk-1, -1, -1):
       
   219                 if l[x] != 0:
       
   220                     yield (i + x, full != None and l[x] or None)
       
   221             if i == 0:
       
   222                 break
       
   223             
       
   224     # calculate and return the reachability bitmask for sha
       
   225     def is_reachable(ar, reachable, sha):
       
   226         if len(ar) == 0:
       
   227             return 1
       
   228         mask = 0
       
   229         for i in range(len(ar)):
       
   230             if sha in reachable[i]:
       
   231                 mask |= 1 << i
       
   232 
       
   233         return mask
       
   234 
       
   235     reachable = []
       
   236     stop_sha1 = []
       
   237     want_sha1 = []
       
   238     count = 0
       
   239 
       
   240     # figure out which commits they are asking for and which ones they
       
   241     # want us to stop on
       
   242     for i in range(len(args)):
       
   243         if args[i].startswith('^'):
       
   244             s = repo.lookup(args[i][1:])
       
   245             stop_sha1.append(s)
       
   246             want_sha1.append(s)
       
   247         elif args[i] != 'HEAD':
       
   248             want_sha1.append(repo.lookup(args[i]))
       
   249 
       
   250     # calculate the graph for the supplied commits
       
   251     for i in range(len(want_sha1)):
       
   252         reachable.append({});
       
   253         n = want_sha1[i];
       
   254         visit = [n];
       
   255         reachable[i][n] = 1
       
   256         while visit:
       
   257             n = visit.pop(0)
       
   258             if n in stop_sha1:
       
   259                 continue
       
   260             for p in repo.changelog.parents(n):
       
   261                 if p not in reachable[i]:
       
   262                     reachable[i][p] = 1
       
   263                     visit.append(p)
       
   264                 if p in stop_sha1:
       
   265                     continue
       
   266 
       
   267     # walk the repository looking for commits that are in our
       
   268     # reachability graph
       
   269     #for i in range(repo.changelog.count()-1, -1, -1):
       
   270     for i, changes in chlogwalk():
       
   271         n = repo.changelog.node(i)
       
   272         mask = is_reachable(want_sha1, reachable, n)
       
   273         if mask:
       
   274             parentstr = ""
       
   275             if parents:
       
   276                 pp = repo.changelog.parents(n)
       
   277                 if pp[0] != hg.nullid:
       
   278                     parentstr += " " + hg.hex(pp[0])
       
   279                 if pp[1] != hg.nullid:
       
   280                     parentstr += " " + hg.hex(pp[1])
       
   281             if not full:
       
   282                 print hg.hex(n) + parentstr
       
   283             elif full is "commit":
       
   284                 print hg.hex(n) + parentstr
       
   285                 catcommit(repo, n, '    ', changes)
       
   286             else:
       
   287                 (p1, p2) = repo.changelog.parents(n)
       
   288                 (h, h1, h2) = map(hg.hex, (n, p1, p2))
       
   289                 (i1, i2) = map(repo.changelog.rev, (p1, p2))
       
   290 
       
   291                 date = changes[2].split(' ')[0]
       
   292                 print "%s %s:%s" % (date, h, mask),
       
   293                 mask = is_reachable(want_sha1, reachable, p1)
       
   294                 if i1 != -1 and mask > 0:
       
   295                     print "%s:%s " % (h1, mask),
       
   296                 mask = is_reachable(want_sha1, reachable, p2)
       
   297                 if i2 != -1 and mask > 0:
       
   298                     print "%s:%s " % (h2, mask),
       
   299                 print ""
       
   300             if maxnr and count >= maxnr:
       
   301                 break
       
   302             count += 1
       
   303 
       
   304 # git rev-list tries to order things by date, and has the ability to stop
       
   305 # at a given commit without walking the whole repo.  TODO add the stop
       
   306 # parameter
       
   307 def revlist(ui, repo, *revs, **opts):
       
   308     """print revisions"""
       
   309     if opts['header']:
       
   310         full = "commit"
       
   311     else:
       
   312         full = None
       
   313     copy = [x for x in revs]
       
   314     revtree(copy, repo, full, opts['max_count'], opts['parents'])
       
   315 
       
   316 def view(ui, repo, *etc):
       
   317     "start interactive history viewer"
       
   318     os.chdir(repo.root)
       
   319     os.system("hgk " + " ".join(etc))
       
   320 
       
   321 cmdtable = {
       
   322     "view": (view, [], 'hg view'),
       
   323     "debug-diff-tree": (difftree, [('p', 'patch', None, 'generate patch'),
       
   324                             ('r', 'recursive', None, 'recursive'),
       
   325                             ('P', 'pretty', None, 'pretty'),
       
   326                             ('s', 'stdin', None, 'stdin'),
       
   327                             ('C', 'copy', None, 'detect copies'),
       
   328                             ('S', 'search', "", 'search')],
       
   329                             "hg git-diff-tree [options] node1 node2"),
       
   330     "debug-cat-file": (catfile, [('s', 'stdin', None, 'stdin')],
       
   331                  "hg debug-cat-file [options] type file"),
       
   332     "debug-merge-base": (base, [], "hg debug-merge-base node node"),
       
   333     "debug-rev-list": (revlist, [('H', 'header', None, 'header'),
       
   334                            ('t', 'topo-order', None, 'topo-order'),
       
   335                            ('p', 'parents', None, 'parents'),
       
   336                            ('n', 'max-count', 0, 'max-count')],
       
   337                  "hg debug-rev-list [options] revs"),
       
   338 }
       
   339 
       
   340 def reposetup(ui, repo):
       
   341     pass