From a9af3e7c66c084acceca089ff2da78b0c44b905b Mon Sep 17 00:00:00 2001
From: Stefano Zacchiroli <zack@upsilon.cc>
Date: Tue, 1 Oct 2019 16:19:32 +0200
Subject: [PATCH] swh identify: add support to compute snapshot PIDs of on-disk
 git repo

---
 mypy.ini                                   |   3 ++
 requirements.txt                           |   1 +
 swh/model/cli.py                           |  43 ++++++++++++++++++++-
 swh/model/tests/data/repos/sample-repo.tgz | Bin 0 -> 12201 bytes
 swh/model/tests/test_cli.py                |  16 +++++++-
 5 files changed, 60 insertions(+), 3 deletions(-)
 create mode 100644 swh/model/tests/data/repos/sample-repo.tgz

diff --git a/mypy.ini b/mypy.ini
index 656d7bb3..fc409768 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -8,6 +8,9 @@ warn_unused_ignores = True
 [mypy-django.*]  # false positive, only used my hypotesis' extras
 ignore_missing_imports = True
 
+[mypy-dulwich.*]  # false positive, only used my hypotesis' extras
+ignore_missing_imports = True
+
 [mypy-pkg_resources.*]
 ignore_missing_imports = True
 
diff --git a/requirements.txt b/requirements.txt
index 59623453..236db633 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,3 +6,4 @@ Click
 attrs
 hypothesis
 python-dateutil
+dulwich
diff --git a/swh/model/cli.py b/swh/model/cli.py
index 853efa99..991bc46e 100644
--- a/swh/model/cli.py
+++ b/swh/model/cli.py
@@ -4,12 +4,14 @@
 # See top-level LICENSE file for more information
 
 import click
+import dulwich.repo
 import os
 import sys
 
 from functools import partial
 from urllib.parse import urlparse
 
+from swh.model import hashutil
 from swh.model import identifiers as pids
 from swh.model.exceptions import ValidationError
 from swh.model.from_disk import Content, Directory
@@ -17,6 +19,15 @@ from swh.model.from_disk import Content, Directory
 
 CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
 
+# Mapping between dulwich types and Software Heritage ones. Used by snapshot ID
+# computation.
+_DULWICH_TYPES = {
+    b'blob': 'content',
+    b'tree': 'directory',
+    b'commit': 'revision',
+    b'tag': 'release',
+}
+
 
 class PidParamType(click.ParamType):
     name = 'persistent identifier'
@@ -45,6 +56,26 @@ def pid_of_origin(url):
     return str(pid)
 
 
+def pid_of_git_repo(path):
+    repo = dulwich.repo.Repo(path)
+
+    branches = {}
+    for ref, target in repo.refs.as_dict().items():
+        obj = repo[target]
+        if obj:
+            branches[ref] = {
+                'target': hashutil.bytehex_to_hash(target),
+                'target_type': _DULWICH_TYPES[obj.type_name],
+            }
+        else:
+            branches[ref] = None
+    snapshot = {'branches': branches}
+
+    pid = pids.PersistentId(object_type='snapshot',
+                            object_id=pids.snapshot_identifier(snapshot))
+    return str(pid)
+
+
 def identify_object(obj_type, follow_symlinks, obj):
     if obj_type == 'auto':
         if os.path.isfile(obj):
@@ -73,6 +104,8 @@ def identify_object(obj_type, follow_symlinks, obj):
             pid = pid_of_dir(path)
     elif obj_type == 'origin':
         pid = pid_of_origin(obj)
+    elif obj_type == 'snapshot':
+        pid = pid_of_git_repo(obj)
     else:  # shouldn't happen, due to option validation
         raise click.BadParameter('invalid object type: ' + obj_type)
 
@@ -89,7 +122,8 @@ def identify_object(obj_type, follow_symlinks, obj):
 @click.option('--filename/--no-filename', 'show_filename', default=True,
               help='show/hide file name (default: show)')
 @click.option('--type', '-t', 'obj_type', default='auto',
-              type=click.Choice(['auto', 'content', 'directory', 'origin']),
+              type=click.Choice(['auto', 'content', 'directory', 'origin',
+                                 'snapshot']),
               help='type of object to identify (default: auto)')
 @click.option('--verify', '-v', metavar='PID', type=PidParamType(),
               help='reference identifier to be compared with computed one')
@@ -116,7 +150,12 @@ def identify(obj_type, verify, show_filename, follow_symlinks, objects):
       $ swh identify --no-filename /usr/src/linux/kernel/
       swh:1:dir:f9f858a48d663b3809c9e2f336412717496202ab
 
-    """
+    \b
+      $ git clone --mirror https://forge.softwareheritage.org/source/helloworld.git
+      $ swh identify --type snapshot helloworld.git/
+      swh:1:snp:510aa88bdc517345d258c1fc2babcd0e1f905e93	helloworld.git
+
+    """  # NoQA  # overlong lines in shell examples are fine
     if verify and len(objects) != 1:
         raise click.BadParameter('verification requires a single object')
 
diff --git a/swh/model/tests/data/repos/sample-repo.tgz b/swh/model/tests/data/repos/sample-repo.tgz
new file mode 100644
index 0000000000000000000000000000000000000000..5b5baa70b6be89161eebc0cc9b7589481a439504
GIT binary patch
literal 12201
zcma)>Q*$N^kcFd(ZF}NmV%xTD+kRu)#>BR5+fF97jd#D@U$A#wUDc<mug-bu5Jf|S
z6sslcfq-A>L206|Vd{&jt|HP{N11@fz{%$8A&=-Qk{3XV&2LcGJHgqbC=Q@5Xd0s$
zV?8dHF^vkdBFn`5of8%mL;r=hP(F}mA=EJZaB~%eEU4wGrT_0|&!cx^Ba`cOn(sA-
z>vfjT!?l@4dcw=HgDN(?JPZU}5rV`PB`RRbUWQnh5o}Bf>^Gw<1lc|#bx=eWYB?P|
zH(yOC@q%a;&THjkCHD_UXl=}oS8S>HBr>|}zqhI9c;Y`#@hSzm#8mR6q|mh}R+9(x
zYXb8f*vK@sc0#u07%43Pu9})e=E`P32GujL9}D^={($|n4KiTXt`&%RSob2(AU;{X
z`E&1lPZV{SGV@adTHuoX-Zw&J1I$B#^^<c2t6Bn*p_qIr{H?D6S|F&-_oW@WVcJ8i
zEC@uwe2hUmcFF<O1&=@Ck1SW6YYMbqkdg&~oRKE%-#XxRivn{C-oa@;hwh4g-^o;F
zc)%uDnzg>XxcYsOeYUH7LEE1_n(_ibN18fIV$N@qfC~Yj6MpMEpsFC~r>Ni0xjK9w
zl1@!r?HiEPKe;6kYSjAE$2O0Gif*sUfnGB&;Vq(sUv{J!!9kVUKsds~9jlJSZd~X?
z@f#<H7#Tk8(3vpMN_bX@_fPmSFQN)MiP#YYj2fyKG#LWbG-Gp2!i1H47#ed9rBP0R
z7ku)BlO}BKG0GkI0d1FmH;Fi1@+0<8VSCI0Q`#U`UV&zixF=WKU-a-KWl?ElY^3xa
zeX+}@9Q7DQD%r;r^gx)9G-lR0Fm>Lb19)j;d=W+ni9*gG;SwalVKmI}Lpo_?I>afQ
zjMOQwED2Arv_v|}WMyoTL#mkp?3jsRI))OhOn8(F5!gp~g(7BUir=2fkWr+Z`^&}T
z@@N2R5R~xXC`0TVu+$(H_!wsNG1L@%=y9iJ^kb$ZVy}9I)oDh#U!PE{v&n@6Wc|iw
zszyNQxU@}yz@gDof%WyPJy=0vWm|i=km;6RO$w}gB$~aJO<a>+`<nrm2~5n`MJ3KD
zsm`p74q;|ogd|Q$K~gCeGXe{~9M<n$>|4QEJ5&@>fpsP@sHrkmT;XW@3$ccDG0y#4
z6Vz#Cn(!oyQIs?0I2OGr=4-_rlu?OoHk{o-z_oFK6YL(ba1etcU^r?7{ZAXDekFi}
zu33?+y!Qttq}cZ3zfBFmWG`SuazDZe;>`kGNYIzogbI6%N?3jYA8wlmYP&Ke!AHEO
zI4Z`$(|{0YB%1k7B>o-DnL}=OpU;N8-qa?rz6`o--Jl}77$utz;!~V?ruoxZKH`So
z#h3lsq*6!lH|;hz|MW9|CK8W_klW{dw|+q-|C{cTOv|(SdO+NZ;A(9T@gi(b8|Q4>
zl}9w(zwK}s%gI6=3F$gD6`6F-@)pZ$8Q}1N7`n+o<u{x16=ze5wzs^)ljn9O(DfKr
zGi{OgYp9gF?fK_>y1Hyiy-XCyMeg>vQPm;g{;Emj|1`fuZv^1Sb2CvNjFF{m|9RDy
z@FV5lb@`FlNagF3pY>eleN6tFYtf5JWVE=)r8GXeRU$`Fp}VZw9-9a1x5<G@@Tlj(
z1`xO+wx7BB7^d{T&YPP3rJ}p%;`YtWdpW2V@9X*&@EK{>YKxc9I2pagDClmT$akHw
z^f_=PghsWv_S-^=$V<?A?<AiR_sfG*9O-qs>DJdT>auW7CEWkveG@|~4wz(X*U7ua
z&=GDrT{AZ~8q45G-l5~*l)vnalfT=W!#7c*=<T?CXyDMox8*4HwSj#9zIXjM@vcT7
zHWT-<&qfN(#EoME3h!7pUx3VNrfBv0wJ$yW^`=mrJ|5u`pW->T_byy~<3RO0sKt2=
z$Bw6~vwHG9^vAFB&D#2Cdj*QU^ebq0Iy0v*E6?fb@IdG?YiM{^^b;)|O4#4O2N=`g
zK@KSG8hiiRh)14L`Dc6ZFTCN@r72H$?&~3u*Y>XL8Y!mNYR&Fq#Nu8{_9M@L%%S5u
z<!fX5_w|kJjmK-y(DJKs62-M2=i!i@&35F0Pa)Zu3nJ01j6ixinV2aQc_YKd`6uFO
zvD^4;Vh*nd-EGdW$jJg1k9mvs*U(~%#*4?hQ~q8-p2gDQQt9k<l<Q5`#b?Snf|yRD
z_AjtiCNr%zflLZM1cC{kw532!`uF4YAKurc&tl3Qg))M4bB}R*M#Bx<F52LoS^)ST
zq7HImsoj=^EKKUyQ#R{nzb+L=#c0toge|eHlV)cv#X^3xLo^X@zl+E3w53OmXPvv)
zx6NJ~EIfo&*)K15{hiz0y1gJcW?hBLZ_|p$t3MZWHUpT{lvHiFUr=6k7aZ)BMK(G$
zXj`vm<2hP-AMMLL%r7r$05v85y?eZ<Qi2x^Y~HAwmzcSHAnVogEQU<7g4sr_U2!x`
zr*2(=lKoAXPu|?2Kw{|4p@&7-b6?ImcJkBi0li%5P=BpDxr2bfp<X=Zl(xY|gP(-v
zg9M%q0`Bt3ZtNTwYqI<&g=-I1FYD*P%BsL+>9cc1y0ha=l)r>?C-<>$73_~&M|nVY
zZYSYw?x)VG<bB8OMcm-*jQ{esK*9Thd*AWy9IdwVD?GRPG0&s&Tr<TBcWX+128uE{
zzYkM{n?erL!9pt&<(~mQMC{NB+eReolk{sI$jm3T9;I7K*uk@Mua_z{*jEy+38dDm
z6ndncHskN+?M!fA2ETt<XS7>8t0h7bSA76!kqns*GG*u2cc-c5+1T7Z1kX;}&t8JV
z*IP-KjGEmB==Zsixf?09%qhge1-}m{!kl$7Q<16c>a|VtW`RYMm7S++k22ooF3tT4
zgLy?oY-3f;EQbd>LlA1v{8>wOGrHiJz4<>Q^ADcF_C|isd0$4?g(M!B)(QxCij+oQ
zyjb3VkChLtV5pZ7vmGK@ygs6nw7oMMn5?N!%56>G-?b78^Nb~P+b&iE;U)stkN$#}
z=Clv)d6Rv>%bIKYG<^OwRy#s52j>W>LC3p0A%1*DNm8J(G?w4Ov<;6#t<R51+UvK|
zT9Sv00<-zx8h=J4VNOS9ul%m(SywW7;;G7?7?{okG<;9`<B6k8kqg!|I`3Wk$yqXv
ze{PgWH2!y*N2E^3UUqJOwrw2{f#!9_!J|Eo%{dtey1v?ZaRB^68eAq;5{5@rf5%p9
z*uDL!fa&Upez``oDggE#i{I+ntQb$Q!;O#oN&Imf9N+KkQTDDopKX{^J(fno;o42i
z`j0*f!cyZxceowyk?b*G)r_-;_&Gm{JCB<WFim7hA(YK`b%UgT&FEkX4%5!(sIyX+
zzFYdMTYhZuC_Zmb%fo#QP!X>xb)@BWwM)E^t8Z7<uoRHuqLLn8^QRFwcynmlrCj>w
zkN-}&xJ+aQIZz1osg0WyYEnKL&IfNG?wg_ul({}RQ;m^|B^nIKI#Zi$#!H8SSXciz
zBo{s)-@2n%zogc9E=-`b^Wq+bW~;2gT<$eR5PqRthz5+f>=htyZYBEX03tIAfMqXT
zWUh<?D#9}m0SC(Me)84sbOlmSGXM2)1nM!xrziShsA4JhFst@u;DR`R2~ad-F4t$^
zu7I(%60>>1u+@ZQ^#o=0L2_kE&DIIgTL);XCu^@zu<4B#b?(QOF>B1h=dk?k_Zmzt
z1KO|F-_Ljr*kr*|W8eNM<YSNBXl5PwB^_t=a?pmO0N0cQ)#WbWSWu9x(q$tr@ena{
zSj)TBN_Tj_Iri9RwTXhu!tiq0zL3lFoZZ&AD}CB(bBKf`7`_(2?NxId^tUrGv@r#e
z<a+nsW^Lho^xreWkVLa>!lL=Z)m9`@ewe@&?>h+XMx*okwbwZxFQzNeee>r{NiDs_
z4bjc^JTQ*4dH?;@=j*S?E)LT$+pL4eVLstr$8C8hd(~DJ5<`DuytY|PPU<_Az3zpe
z<ts<_1CQ7AhhOoObshSTXYd><8Kgbg&cxsrnYmnjc4bcjdRQ@0_wu&I?4-p55|;V2
z|L#k$PUc)TcWdS%tHWz7`=x*P!_BD{<?$AW?hmvJ{|VQUz|BBpn(9K(!Y9HMRGHL2
z1^G!&cRz2EvP@S;uY(17sn8D4*F*6ZnRDb@ZhDfpm$u%rkG9E;BwrJ$&qH^Yhb>xu
z_1|`O>@Brd?<HLpc&`HAJ85E5C6|9!Z#G!Ygch-OUHDEMe0|A*h<HkKm#(upO9o+>
zvVITk(WHfzVn&|PpjVr4J6WV89AhnlOI<SfJ*Tesgt*RTIaBvca^AA=&><Yp(vN4{
zS-t+ya*hsIL05>MA`f?h+VcaaBDy-EJUbcr-u9P6IAdr8gR2nTI)K+J|8);O3hKUG
zw)WRu3Zk>#oC7<o?tG;5+a!Fy7*`;*5V`Xkmg7KffRA~g3_sEt)c&MSupQ^y-mkA!
z&`F5CK!6K0C~yy?8M3(qZQqGe%JhlPjVK5RvXTq`whem9S4ZcIdr1K2kqT*5i|*R)
z!emUw{zO5^3vOEvrjHAxUhXbo4l*$L!U}cTdaGl*v;Ky=2WBgPS6crd$tGc(0F2B3
zH~fJWUc}!=@6~D)11(kS0clH2+F~jCg-SY=F_l+D)X6}HETB}s@K5;6j}76_inaDg
zL&7|WD{PNA-X*wGwJ_2-83uQR1{K$Mjcl@G@Dr|NI~e}yr0$WG3k*QvKgE*KBo$z6
zx&K!<N-_P<|Ivn7GDsC0kf<!?Q9vpCrzS|_Y|mIKpl$(3X0-K1R8=7TtwRh`|M5|#
zh~IL)hs&q~JVZ$C+hze*8v$~vF#w@380trWt01-%)A><^)u*1?HG$#_@MsnD)+w;%
z*@|BNVN|)o6-qwLvL4W$o|*7uQb_@wuov#EB?R6gJuL-2Fy|85v9P%FZ5H{CkCYeL
zxw4A%C}xeuC^)gwCG$QatW(0m%*u9Ev!sk$KYNBw1=d>v!b<HmKnINF#&4D4-`9KF
zrU}-ORKIB&fPm>nDYeZ$YQKlr$@0M;6tG%DAfvSoaE|7`<C)o@syHW5neS*Pp&Qs1
z;ktrUJy~;#YI$q-0k(gIf(p&bk84@CY16aCRcX2@%{jUSwcR8l=y2O*?>M>Ur#EU6
z?66`}6Ob2Ec#F;p+M#(Y9qt50CC{?O>t9DgE-GMEzp2$1e^hhW!6MXguRKLWVgMDE
zX~9Pgqxbe4^kdk~+#?XP2#e<f2#zR#*Tmb&@4mbJ7F`^*Hvqh-55@yfaq9y~J#Yrt
zxAM~$=JPB|J{Gft9pFkc0};wSc=RbpQgTPpaJ5O;qLWV^+O2%ToCF($^@m1z>iO&M
ztEoL7+)f&B?Iz_IORRjC5y?93C$P9~$FYW&-^|^K%~%m#w+PY<PM037T|ENmjYPm~
zx?j)a_X&^BPTt-(VjQC}3mlqXd<G~kxBpO@O#vHV^>_PibeQp~17TkrHfr-x)HVgA
zQ$K%@!ZufdZHUeIw|W!SUM=)#2<pPWK8bKy>w(x+aln4Yz?9D)C`7dXa2j?R!NiX6
zAU_iJWzKTBn{@E6nW`BOIxO7)sP#sk@E=tND2f*y4-UtbsJwf{s*7DxIu>-5D~a?X
z)48KBf~exo=RSo#X@gU`L-arzg_osr*Ox7e&P)G>CY3FU?@U^<{@k@K$HllZC^YX_
z%RATLs%!+{(|&%9g}&vJ+g<{-fB8W|K4W6kEug%t+y&ZY*1aXwNIp&1#<foPC0cIc
zI=)p#iXx^Se@0))^bx|;BH6VlqG~tNpFCP{@w?GZ4Pd>=(j6bt^0+v=Otfaq<vRP~
z(@s@jZKV6pxN9oz^%^dThk#nBYbpC@qkNZE178Pfa3+c%<)`AJVff~21Jzm-eaa1i
ziMTVIaFif0?D{je0!r<6-x^i>zdughXkxXYp?Lp>4hUk@ys+lJbDOCa0#J*}`!0yY
zZ13|^=No#`h030jXqWYsa@46Y@mzE6q67!d{9^iwtZK9i<K{D8AR%8~UI=kVDwK#C
z+R2tx5S*As-9cflA(@XOVQ*c~t@*G>x-A3vY3v2}U0)@8_@z&;k4Ki^DWR~S=@}M@
zUsWx;r8hG*uns+_3ZO$1;~An3HOG;EHRR-(JPD%qOU#IgG-rhh%2w`U4B9Fo7hLd;
z5DYXJC5|*wxhkU9ECm*^s-n(xvOw0Psa&QuATN%L2ZcSsVn7XtE;nlz9SXjNuYj_b
zQfX#HN?-O%3CEU`#b{W<e^3SMTcFF&1@S>c^ijYN`;pg#lb*xilb>=P#2HAItCZuY
zEZT+`gO#zTUBLWW{E#Fl2D3xf1+n7#T>~ZYYjyNVJ60UY%Cld5J$lu042Jov1>XW)
zbWChP`_NR&tD2aU6Z#36(bv&Q%#v7MZYnj0I#r__4TkD3r&k+!2NwE@QFq|dXfNXv
z;>h%KKzMe+FO_&?RBqV3KVM*~qgr3j-W-aZG?>n}s=DW><;jjsdV+QRbry<1l+myy
z9ZyQjZu;a}L7N5G*uSDG#tlJ#%s}5iU_Y|R-LI%5!GBiL)zXzPMzUO3F(D_x=;VHh
z=p#S;<2odZ3+9}+Lh)>XMZ93ttzi)r%j9=3?u=j5VU1EJ4bq8Zw9hLNBJheLAPGSH
z4tV%gGj4U@AAk>EgL!ofxYqM>*&0m(>b#+&Mc_rj4#+IqP0S_cV)Z>Y>G9KanN2W9
zhzJXVu?lIc+a5PL06Q%}?&gn0Hg*P8CnyHEyuCYpKC*<00<BqdewnI5YmI64n^f=C
z%HCYFw7jNQA}&5QSMmeYPXaysoiRt$@@`#4vQ{0Gx>q+g-YppUIUhBwyB^hkNMWrA
z3E7ddXa3nr417w7s^hWg%tOa}xbvPX5Ani|7P)*>$kDEPP&RmY+pc1_RJ$&!u*_Kz
z+rZd`Co0gRK}V$ytppq7^YZ$?e5mQ2&&B7js<GJ|ebwSq%IA>8a`M-$LbNo4bPF#^
z#m30})c{DS`L`qd2r<W-l(OpUCuy+CPReY;eN1F>!&Qc0a+sZvww@kdQ{U?amSx__
z@4S&8nv&EIIsvr=XH5Pr-;c*GMHEMCleF{Yj^Bjcm8pDoi{+I23;V@FXHXHWSu#wO
z3*ldS>kI#5l~%}<xWw(0n#@s1uJ`iP$%SwdoX&Ujh9YPAj^Gy$#2Vi1FlLd*E>dt-
zLDBw#Uge6tS*q?oWS1)K&EG6vjfeC-%jZEs8S_F+K*1$(0(OJm<IaJnlBIIPiGy25
z`43J+_!9<Npt!{u``HL`pT{rRu0D;6h;kRMkW`;q(v2^(*orn>(NQu<k#3SpUNn}w
zdASAH(^al1M4+nk{$~+2VAPDw?u{U0CeH)c3Zg;IMD_x5_5Ry3t$cTrtgLKs$5$0T
zuU91!3Ph+{^z=O_4OVNZd^567A?tQlQgzF5F*>IbxCcBJu?WSR5)bVG{6Ew<DwGbk
zq}Sv}3X7>}#jJm)<FYJT>>xQ?;^X4Vr{F%qL4MzblR*p5xO!7S@V8KAEyR16r9liP
zV8Sl;D=bsu4qR7ByJ+mNe02dp!;EWLyFTKCD<Kl3rg?BiMDIm+g$3J?Q=-U?5e4t`
zHv}Kt$i$dx%uHL4a=XKI;CUk>#~1%y0hMRk!t=Abyzc=D$#0%-V=x0~^N!u^;u$}e
z$4zBCJiupEh(tB=uZ<HZ3JlZ&87#WFq+d1p0)55LkEfP?M*EUy>D6gX3L)253Zn;U
zO}9(Cr%<#T=)^hcp3x{My>;5MH>txLK=c^#i8JFHo*D1?97{hE(0Gb(B<){T)k)+O
z17NoW+E&%81v*0i8X%CqC>+$Xmi2qR-_Azh%mnWI412g|@O=T2rPRd_i6IKym`-vL
zh8YA<`FnurWnn#+w6(1AQPT%F2c#tFCy4^{&6yx0$;Z)VFN5#;GHpS+$jH6MS+Rhe
zj?;?L$sp#8h<3OllnM`0;l+@x%gV1R8G%jEbIeonI~Yh|dJ-RB`>OieJP_P7Lrn$&
zly_YS!Om|2SaNGf6R}jZ3%QiDj2Dh1`-HsW_82J={?ixi4ViM3NYfh2*TSoWJc9NR
zhE2Y|NQnYYW(={mZr3M9(dkl}M()5eyN@8iw`6XsV9(>jUqQDv3}i1EI*1Lrs}zbT
zlbyj`P#zF&`p%2}-+>whz%k03p!Cy!U{SMscK+Yj$EctAFJ#`UE=&8gSN<xst-!5o
z`!<$C;D)V3+G*cyal$v!mH?kw_B}C9Ui{x=6=kEItczxCJh!i|k{`CnB2j{J1HFF-
z<SfnCIr3N27ld-giIP3UA*JRbgBjBO>^O7&q-nmQ`2YoPH%!OjyD082kR1i`l&jaA
zH1VEMIzmm3V6p%){BBUGt1p}w!?>=)2`~;27;swzIuxxFiek;&LMZI{-~iTD*Ki%R
zP18un+=&F%)qaAzbXIIJQ1&Wx$U&kHPit@l?$SVf!xF9{gUp&bb;~6r$f5fV?%>_&
z_}Im2H@Tg9^L`4?&_h-4tegRRfne6@yCirrh0OG)DCZc%oGnVrZ<lubgzpJFBQ`|-
zxGG5IQPOMVL$Cn1pfwjG{&vXUC|5sCri-sFf8+Q_!F7>}!jcJN&~d{-(%|~(zHO0;
zw9oV*cbjPK@Gx&V`_(nHTBYbb=!AuA>F-@%u7tsqOH6b?!!eHAJPPujeCK6862o6p
z9xC=dOc`~&vz5B0)(?DF|Ba+FX2Fkf&HFQse$x5bTZJZ?c|i~a4wB8UqU!ttx!H55
zO@is{vN`Ch3YRq8?N2J)6uR(swBt3)PQn^N+U_M$^~{2`;gF+RhTjxs4gn>n)<h^F
zEL)$+|1P64EtlN8HHis_sq-u7DrP&!%RcTMfhKD-GUd8`^z#3K=cB)qM{5C^NNgxX
zno)p=5ihPh<!m{E(}6?c1n)uV5Hkcm^ol`q#r-pRr>PPvz7Q-STSZRx{}Tx=ELyO8
zK?Va^?17`l30NGe=(Uw2<?3F9#_z$-&=bDE-iZ$aJr@kA7H7xn2_i&eN8|a{wQ#|=
z!H$rw3L{S+zXFd%KS6x1X2ga5;-^=D-nrn-1lc;nY#y-ZmGfZj{Z;jjjS#oRGCtvW
zzgn$uoNQhQ_nc|a@mV|H?IQKqO=p?sq6dn2XK*hxW~g1%*YB}m$(6u-#ly~cqCTsb
z@YLpb9s@z5maI5I2xE@abS7iaY$!v@%-Th2A_RwmfKA~|?}Uk*fp)f-0?_3OtX$+|
z95<CVO&O?qFLi6K%12UW|EDe&tFp;{#4E)AnUmouNrvIxi~<2X2cQdvKf3NCjx@~s
zc0H6H+jhx&KB>qx{Az9%=2_eAwH_joY&GY;k~mB~G1{)Sx!aWZLi^GQU^;^WZ%K0F
zTB5Y=j&kjO68Elk_4J4Pyun`?1~V;sgt`|kmj?Y&;4NrU(CnO78xj1in|ZtRLgtSG
zl19;>5JU%XG@4myO~onjD^?m`IjhT>66k_X;``$evk(5d*X8kcw=uxPqZF0h7{ay9
z$eGzrCZN|&bP5{an|-viHtxHrZ_*bSH&R0s)Vaf&yH6o6x6PAQqQ1iNhHK%DSWaBw
zYH2fnxj|_TH%=Q(9#qOKtQwR%%ohk}|2txxnv#MqQi&91-RB|pDPd}K=BQSJGr$53
zC1Vg8hY>p~Ecmxf6*J-&B%F-oBqws^tsJTGrSBXX-m;8%0N6FS$8tQeTV&dJo`RIf
zQT-g9=;(~JwNW`ZIfF-km&tH$Q?xR;M8Tc2HBy*pr=`5p{x7SGxLK{?Go1e9eji6c
zFg^LjZxjfTX;V{lqEvBWc%dx^@>kC$s@c>MauO}H;ATt*=5Pj2Y3v0>CB1prb|~By
z;Ra<Pw7B8Ti>&f(I#dnH54?9mA1@{smg-TQuQ*I~;`yE^+0<uJTi=LrS9bjrx(^nd
z28#&)W*VUH*TvhxAr`kvg5ikn-;^h#OAe)m3QCbxH}TN3<k8?yiJTl1uexTNs2Cs3
z7nT&r$O%gbGz`-c5LubGy<tI0!3{d#K#1Xs%oy4*G5kAv0d@y5!2aqPyi$QDu~n6|
z5<1^Fp<beNe7SK5&7=zF##qb|7hafgJj`-R6MX#?m4x9qtT2SdZOJGhAS&e}6O#<|
zbt1|XwKzcXM)1oaNHK|Oc%a%rlQy@ieiAx&C69S`KW<|vzJU?O$ndFpxi65E)P0do
z_ig-=r4y-5zkl777~@nj#VzkT*c-ADcN!J)93OrKK83oI!MM{zoC%J_n9dQ6W6q4k
z%1lXU+f)hKsH1bVdKKe&ADJ*TP~Eagz%AU$p0zY0JIKqr+1(V!Dj1fKt5gJqjf<v+
za-cmcN@r$ejoJorr(yU$gEP+MI-kPZeqlK)?;UW%Vto)OLw~=ff`qAVJ<F@3SaydF
zAbd{yuE;;NVTS{FaphELR<D^LBb8cTc+0uDrOS<e$RTHtEnYpQJr~HUfP@p(=d#m_
zilzt{`jhM^hAv1DJVlRQfzRiV;Z~<fZJ$`!qlq)Q(7E^A|JE(r%96`2aw3go`r{i)
z7OEv`+Oi?#l>>f*foiDE9<(f}6-I?<UR--N57pT>4I0p_$4o~QF$`y{+%9n=W%$j@
zaYt)AAAAsU1R80Nd@me)RAZRj0}^d>Hpn&{M_et!fNZg*R?CV-5%C3InWs<(Eo(uD
zZ70HE>aY?#-a7Y#O$Nb$6`H05{IN&cd8OC*0Q3-l+#%riuN?tebapnr`es^~fv@(H
z&A`_OUj}t35-Q7NJbBAknKwU>yVl0K6;>0wH{F4UF^ZjAg%wP-D8K&3CUqqgl>8nV
zH}U~Jf1vCty1#}cdG+vAynBO!Bh$d~9{YKP;KBaObT&$|9yG%1`(=6`p@7fN#eGxR
z7}T7;fS*}6`GWqGHaE|)|8N`X#^ibYABVOhN(y6hmxqDf16{R?kPFK`at-6Y;x|QX
zp*wNw^r#si?zad==|96!p2~;FE=~QNuKI%jp{X4`L_YAq#boTRC(t;V#<5o#TH3@9
zsTLcy0DLIM5TN}B-PC`dj|1+t{9fM-Y-)CPGE#xL58rhjD)uzM3EX($kOtk=>}`Eg
z7~>wnPj)Q{U3Dl_o6B68bp`(>Id(9sEy`pe<am{rp$w(<km6CgB-fRSj6BnYh5mC<
z9-i$3#}s%8l&>*xPy2D_7~wdo#?h)rmkDULL{ZJLwms4(2-9#znv;2g#2k10SoDz8
zW&b54epZu3N;M1MSnDobb86>UBIUg#8ByXbr^-nZjsWb?bY3V~I9XUu2DO99)MrIC
z2&8hH0>Qf!mq|q?f&t3{98+M~3Sj?vU@CYl-YYF>JsonB_j@0e-wg~I@s5qoUes=T
zIhvHO-TgD+IE~y-++Z`KQe)`s57<2wrZdb^15u1P0__{NXJxD&%oDS;GV{g6s>CRp
zomr$U@Q~O%bo)C&^oO+GLrzk4u?R{Gsp|5x<Fw!|iS!u{J=I7cQB?w?b5~OpHRFCN
z%5O3z5T%RMwO-p_;~W`ga<_u#ueoTf@t^lm3FK$LL2RW7yv>0!WMeXqaY%7Hw_^V<
z6xKUd9H@<X@di@3<xjHy2r|!zgMJ(<MN6*;m3_S0s#Hr|xdoV$I#+g_%>PQRCXZ6A
z=OQMZxIfAK^wyP|HP)4llB1rgRnby5=cWZu-w<K-kOAvhW1I?+69rzsInOpUxc!!p
z7SG7d_uzw9>(exmP%jL%-SHK`YU6@ioRMw*&b`eP{t_*?1N|qCA@Y=O^d!K=>3?@L
zu?tFZ{Z{!Q&IKSKXsx_x;(kmyT;cpB!;&VE(dBV_x6ouzEin$gse~j6fgq}R07>T&
zX1VUMG(YRx-b#KW2O)9ECmB07UJv(`-dMkBBbHVAVr-=?Hv;<`l#25fFFp4X5V6Rc
z$l#-sW~!`}_2&Qr!zs!EpI%&E^u~+DkFh(4VGgD(0<Ij4h`~^h%MWfDoeY-_h1@l4
zK{B|+%p~a~rLvjfXs&?5NHS#xX2A=LvCOz#$-t#v%T7W$)@lZ4-1fR@#4SfT`pZCv
zVGU@R%(9iwBB{{3_g*(^ZgKJjC~Ij@$^&*LJF9#vpot1)D}Nsim+P-t`CE=SD@q)S
zjwIDfS8^EaRNXYxHZ>PZ^xS5&A!pea!<_Ouk!>C>nle8G1-Wa9!JDy)cKQrU7y5S{
z_}ZY%OGiryfgI^sKX{m@EOxf&iiTT`6y0NWkeX*8e3Z&VE}a&2|A>Nl&p$ummiXd}
zP+||M(0l!XEPQG7-bLD1f;NtGJ*v+#vthMer2ZHh<hBOH<&@DN;$y2J8pRE&&+&At
zFXbj8VDPS1YP6<}xC*)-ImV&3HOv@+zNAT1ltKhtS!`Hg)59niGx|&@TEuW7#>Rkq
zac|&o!++i2&|@fr!W@tZ6xl5zIhNo4K4%KNoi{&4j&(vDr`wk;A|%q69g}<!SB-~`
z==r2b;Qc#tc?i8Lm4rO`)n9VF^h)*fc`OhQ@cKGh&@Ztg?D2j17y;!w)BFSQo8PUf
z)>@T~lUhFOXii4uRoh5DP8gglcyD#u$zsPtsBG1>T1|Lo1P?`MT$C@Rw!FaTA7m&q
zE4j!olqUI6uRG{cjt0di#WC0FdUOloEOV{$NWTceDd{dslTqw~-lVV^sT>UXT2kP=
zUeku?-rO0ntYUjNM7`1H|J2H+Fzu(}TrEDRlK2xxB;fn>gt1r%B3NB%^>}?CZQ=&3
zU)8K(ErT|cAZX1VqM?ZC<(xpm5k{0}cn(GxkgbdpvAj3@JTHTwh;{fQ{M?u&otQP)
zM&B6R;P=ZJj<clK9~qt?SQ+qPb@0GYrSoycumwzZy&898*xLar!1jMs1IxCkzkwdg
z_FcfPuim=~EXbyv2~4`hxU^IOoTnpnBKV@G)h;7>?{!EP9T7q<E5^S@_}zKEthBwo
zx`RN|_t!bDm4CEufRxKOfQy}pI?y5g7TA^Ttn%~P2>$uF*(V&OChN^D?1^&_A%b`#
zYays}+#RKL_q{v#NNYlO_pN{at~LTrawfcm`S{Y`%;qSw!8XH=Y=AJ%K*{&JgDnr9
z9w*p;t4Yj^x-(fR88<&lp)4~SoNWu0?;qLqyA~FE3I%4o3KDJprocujU>$3976IZ2
z<38V)c`p;_w6TT{Y?Jxvi+-R#6j%neCw<?e2JIz&NuDzWrBwNK{tlUw1C><D$#wa6
zd?GZw?eORit;U@|5?!y*)2lad&)eqZXD)We{r#tmhnforp#F48Lfj@<LKF;HI>Xwk
zuWSF%jP39Bq1ml;GR(2c39Z!=wXa4R%080X$>fsiudWOX-fo{335Oq4;R>Lv#oLkc
zUw)8@kKm{0WO?RAX626UR>^hE&y!AKA`%p3j^(2J_!vpPyp(Q+?8|rz7Hr>c|JO*n
zGB?T>>c9e5ZQ+8jtFYZG`|L!wrZwPJna+R~QxO(21b`1DhCx2CF7_w)FdR4s8K}(7
z3)x@ykhnu0q%1cH#ulG1UgqNp6@VS|d*tGC+j<fMXXwMG=~v+30i&HTFarG%?81oV
zt{Ap=O2e?R2EM#?)Tnb-VjZM7IS1isIBtr8B&bv-E9LXM+D?5mZEf1@)N7^{2a_0R
z+{?y0Jt^}q3_nTVi+m^MX^+D$9sH}NnLSanv~^1%_^YGdhg$hbftH?kWjeOw`q;f>
z?6U;=cV-|{-l3Kyd}t*yp;nxP#);0O4jNW@$Q1kHfK6ww`p=;!R{n6_Ir5kbrfUZH
znx*V(Tsiqi+Eo9(Sb-X|{rm54#Jmj^p9V1S#R~+rXZn?JyqNGoEj}UW)4NQ$=7@+d
z-hY4a4d%kZl~fx`s$4D_PWXLjwBig$iCij56!3m_n7?c$e}4iwB0n>o^?q(gx4-kV
za~FXg9LGBAz%?--au)Naik}f(0!Jz@E+<}T))a=0A-!YB344bzTumsQJL@&moBmJG
zM#SEw=2-8MG)!+k!<0n_yY9+<Ht3N*jA*Il$vcGVT<SXd-uqD7*mKk2vNb=1lwW2m
z_?c;loFD!aw#uVZI#iv(_s3OqFaz@W>?gX#jGJW1j0dLVWE1b!-szZ@>#|~1nL_A0
znzE*2_BNO;iID_z>oLSv)UK#GNaaOf&2N5Dg~yEH1)pP2bMe4R_Pt&u8Hp;pudS!5
zmX0nALVW}Y|B=DH8DRSOf3@1jfphHF9$ha`ZVRx}viQ^WpQ*g>9ok?}HsB>&lS8rW
zp!vb(iY&BO*!UAMj-ev6E_Ml-SKRrOX;KNld!V9Teg!^_>qITkX_I?kll+O}=hSV+
zU!ryng{e;*6o!bb=7i3HG?sJB+S^u*)iY}aOFr)ZMo*SqnL9163TL>PE!>-X-m&3J
zoBO3HnqSsi7Z`HcplSd=ssTEHg`RJK6cWAge#Q`+yJJnKQ`6zoq=cQ)HH7H1vON;i
ze*V<7w6cfz%O5#^3IU7*OiUJMJD{IM9|G+e+iG*NmZt}ve1dSH3FIDj0rl5ZkH$J3
z-3*y=K5BfzZ*mHnhDcxiCb^+>d+eAwF^yzd(^=kmsFw-MO|?)Sx2y0NB;7SN*K*Ub
zsBSQ91iu(b+{#qp)@pq4bRX??BgfjO0AB2meEVt|`iyVJA2a?ZA&Mpg7PiOc6<=Ij
zv{q+SFXEcV+r^1(R?7KU_gZW>f5j$5Iw|vCV{Tq|p_Oblahr8Edeu5j<}goDt4(TU
zqZB7S8~v(E9Mj}OZU{|LId8XSqrN%lE-!SsoADCMMGM87w^dxB>~PQ5Ssf)+^9g7v
zS8EMU0XVab8d6+M*I8N*D=TWxx><6^2oG^W;$h=~0UJKv&(R!SOb8J>F8uJyQfCXQ
zkh7r=i<Yc+M6+lbg=RKIAR2fRD=>FIAqa33lX-qB@kw^GDA-A1y-^IX!}J0K;LUv3
zxQiNu82<Azr*Q;%oh<BkYf-NSIJ1@6pdAhPf=Ag4^^dF?m)v0k^4U^XvUUki3ARlO
zZ|l(2la$|9UR<|_q#oKc50lH(Ff0=#gF%CY*#s@MG%7`_Gb0QtKH&K!bCx6c5z{{Y
za+>@9#g?(eW~a!k9906f%s8(t`aFaHw(~qef!R-*kMOs0GZcqL-XIP+%%BER8s?ua
zOcbP<II8RidUN03DboWWPVRcqissY&KILX7jwyxIEp_II;xp}srdE2}=+)V(O<O`o
z6cwXE7@r)$PH_0TU8>ISd#tfGCB#Dk8;;KQ<jM`SO-bprx()tN;y#XWSU7$=sJLfX
ziknyDC+?YB^IL5}8mtvszVS|9R2o&79Gb(jF4w&yx)$Ewl~Q%E-AJ;A(R-8is3j+Y
z^7rrGS!};^h94QAx+s}u5k|U61_Ie9wjZDwOl6_gaIIydT>=@v?aj+R#SC;FfCE)}
zY1wJNZh>&g4xb-&Iz2#}qL#o{Kuuj;>MgJ{y3zBS#JO(=JLk}C<dcYFb=6|EZ|gek
z-M~Yc8}ItL2BEI2cURx$xwEH!$9c_Xz#RKUyQh9@b6a=M`gN5f%356VV#*Yq)#oN(
zz3kZQ=H<hE4xzQ{a%;!2XhqK<)W&A3$KED-GtBN|9ucbrAn<N!_d3Y-=ghUO$8l!w
z3i9BiwC|>MNWX1q1D+dC=bAq0mr>QaeqT{<&xTdU!HIfT%dUGHFUM0wRejIuGdG@1
zVB1X%RCca?mx`kER&3hJGY?+2f7{BVwtpQ~rxA~?gPp_Mz{~1M)~P#>{k*nKjhzCA
z&*%BzLzhl%U(XJtf2(#OR%h;Y>~+nvqe*s{K2n5j8++%?hw4<TnCE@>4aGtErB_W{
z8_wHhHeV12^mdJWduu~4%HnJ6xjFV7;wpd%2u1Y1zQ%!j$o*rml@SZ9ZR0t9pW~r+
zUa!dKewalaOQfAS4=x3T`swfCS|l#Hk2Hmt**$C4J=P^97(ash>o2u0xayD3CaJvL
zF8%Q+<VUd~ZC@R1qcOX9U=8aJnn#GyCV+Z5R-v7VE>aS_arpTWs~NnB$fj@CEwxSN
l`&PAVJ*Ax1m{LM#MlGW?`TriLfbS0i2i>npAlRTF{{wz_I`sem

literal 0
HcmV?d00001

diff --git a/swh/model/tests/test_cli.py b/swh/model/tests/test_cli.py
index 7f70b46d..990ca08e 100644
--- a/swh/model/tests/test_cli.py
+++ b/swh/model/tests/test_cli.py
@@ -1,9 +1,10 @@
-# Copyright (C) 2018 The Software Heritage developers
+# Copyright (C) 2018-2019 The Software Heritage developers
 # See the AUTHORS file at the top-level directory of this distribution
 # License: GNU General Public License version 3, or any later version
 # See top-level LICENSE file for more information
 
 import os
+import tarfile
 import tempfile
 import unittest
 
@@ -45,6 +46,19 @@ class TestIdentify(DataMixin, unittest.TestCase):
         self.assertPidOK(result,
                          'swh:1:dir:e8b0f1466af8608c8a3fb9879db172b887e80759')
 
+    def test_snapshot_id(self):
+        """identify a snapshot"""
+        tarball = os.path.join(os.path.dirname(__file__), 'data', 'repos',
+                               'sample-repo.tgz')
+        with tempfile.TemporaryDirectory(prefix='swh.model.cli') as d:
+            with tarfile.open(tarball, 'r:gz') as t:
+                t.extractall(d)
+                repo_dir = os.path.join(d, 'sample-repo')
+                result = self.runner.invoke(cli.identify,
+                                            ['--type', 'snapshot', repo_dir])
+                self.assertPidOK(result,
+                                 'swh:1:snp:9dc0fc035aabe293f5faf6c362a59513454a170d')  # NoQA
+
     def test_origin_id(self):
         """identify an origin URL"""
         url = 'https://github.com/torvalds/linux'
-- 
GitLab