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