Skip to content
Browse files

Important cleanup and foundation for snapshot

  • Loading branch information...
1 parent 5105074 commit 78df12819d3adacd09c7af2a97c832117a29be44 Sebastien Pierre committed Sep 29, 2009
Showing with 236 additions and 590 deletions.
  1. +0 −43 Documentation/DESIGN.txt
  2. +0 −281 Documentation/sink-api.html
  3. +0 −69 MANUAL.txt
  4. +62 −0 README
  5. +0 −139 Resources/epydoc.css
  6. +13 −13 Sources/sink/linking.py
  7. +36 −21 Sources/sink/main.py
  8. +95 −24 Sources/sink/tracking.py
  9. +30 −0 TODO
View
43 Documentation/DESIGN.txt
@@ -1,43 +0,0 @@
-== SINK DESIGN DOCUMENT
-
-Tracking module
-===============
-
-
-Hmmm.... notions should be made clearer
-
- - A State reflects a directory at a particular time, or generally
- speaking a set of resources identified by paths, sharing a common subpath.
-
- - States can be used to compare two versions of the same directory (to detect
- what has changed), but also to compute changes between copies of a
- particular directory.
-
- - States are then both useful for revision control as well as for
- synchronizing directories.
-
-Concepts:
-
- STATE: A state represents a "snapshot" of a particular location on the
- filesystem. A state is an annotated tree, where each node represents either
- a file or a diectory.
- State objects are designed to be general and are not really tied to a
- particular filesystem - you can use network protocols, or your own handler
- to gather data to create a state. See the State API for more on this.
-
- NODE: Nodes compose a STATE. Each node can be assigned a set of attributes (
- creation time, modification time, etc), and has two signatures : one for
- its attributes, and another (optional) for its content. Nodes can also by
- tagged with meta-information. By default, the TRACKER uses the "event" tag
- to tell what happened with the node when comparing its one state to another.
-
- CHANGE: A change is a description of events
- ...
-
- TRACKER: A tracker is the tool that tracks changes between two states. It
- offers an API with hooks for processing addded, removed and changed nodes.
- By default, it sets or clear the "event" tag of each node.
-
-REFERENCES
-
- [RDUP] http://miek.nl/projects/rdup/
View
281 Documentation/sink-api.html
0 additions, 281 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
View
69 MANUAL.txt
@@ -1,69 +0,0 @@
-== Sink
-== Version Control System's Best Friend !
--- Author: Sebastien Pierre <sebastien@ivy.fr>
--- Revision: 0.9.8 (25-Jul-07)
-
-
-Changes mode
-============
-
-Linking mode
-============
-
- Goal::
-
- Make it easy to compose a project with files coming from different projects
- (which can be your own projects, or other projects, open-source projects,
- etc).
-
- Typical example::
-
- - You have a JavaScript widgets library.
- - You have basic JavaScript utility libraries.
- - You have one project that makes use of both.
-
- Usual solution::
-
- - Making symlinks
-
- The problem::
-
- - Not well supported by SCM (Mercurial, SVN, etc)
- - Not very portable (Windows, different file system layouts)
- - Doesn't allow the project to be self-contained
-
- Sink links::
-
- - Links without using symlinks
- - Portable, intergrates well with any SCM, the project is self-contained
- - Makes it easy to synchronize back and forth (your link can be out of date)
-
- Usage
- -----
-
- 1) Create a new link DB
- |
- > sink link init
-
- 2) Add links to your project
- |
- > sink link add $PROJECTS/mercurial/... imported/commands.py
- > sink link add $PROJECTS/mercurial/... imported/other.py
-
- 3) Monitor your links
- |
- > sink link status
-
- 4) Update your links
- |
- > sink link update
-
- 5) Merge back local changes
- |
- > sink link merge
-
- 6) Remove links
- |
- > sink link remove
-
-# EOF - vim: ts=2 sw=2 et syn=kiwi
View
62 README
@@ -0,0 +1,62 @@
+== Sink
+== Swiss army knife for directory comparison and synchronization
+-- Author: Sébastien Pierre <sebastien@type-z.org>
+-- Revision: 1.0.0 (29-Sep-2009)
+
+
+Use cases
+=========
+
+Changes mode
+============
+
+Linking mode ('-l' options)
+===========================
+
+Sink's '-l' mode comes in handy when you'd like to use symlinks but you can't
+(because you put your files under revision control, etc).
+
+Let's put this in context: let's say you're just began working on a new Web application
+('mywebapp') and that you start by using a couple of files ('reset.css',
+'base.css', 'sidebarlayout.css') from your 'mycsslibs' project.
+
+You'll probably simply copy the files to 'mywebapp', but if you update them in
+'mywebapp' you'll have to propagate back the changes to 'mycsslib', and you
+might forget to do so.
+
+You might also just create symlinks instead of copying the files -- but your SCM
+system is likely to consider the file as a symlink, so your 'mywebapp' will
+depend on having 'mycsslibs' installed at the right location.
+
+Using Sink '-l' mode you can have most of the advantages of symlinks, while
+making it easy to propagate and merge back changes.
+
+Let's take a tour of the options, assuming 'mywebapp' and 'mycsslibs' are
+located in the '~/Projects' directory:
+
+First we'll create a Sink link database:
+
+> cd ~/Projects/mywebapp ; sink -l init
+
+we then create the links to the css files we want to borrow from 'mycsslibs':
+
+> sink -l add ~/Projects/mycsslib/base.css .
+> sink -l add ~/Projects/mycsslib/mycsslibs.css .
+> sink -l add ~/Projects/mycsslib/sidebarlayout.css .
+
+now you can check the status of your links (did you do local changes, did the
+original file changed ?)
+
+> sink -l status
+
+you can pull changes from the source (meaning updating the 'mywebapp' CSS files
+according to the 'mycsslibs' files)
+
+> sink -l pull
+
+or push your local changes to the source (meaning updating the 'mycsslibs' CSS files
+according to the 'mywebapp' local version)
+
+> sink -l push
+
+# EOF - vim: ts=2 sw=2 textwidth=80 et syn=kiwi
View
139 Resources/epydoc.css
@@ -1,139 +0,0 @@
-body
-{
- font-family : "Bitstream Vera Sans",Helvetica,Arial,Verdana,sans-serif;
- font-size : 8pt;
- margin : 10pt;
- color : #222222;
- text-align : justify;
-}
-
-a { text-decoration: none ; color: #685565;}
-a:active { text-decoration: none ; color: #685565; }
-a:visited { text-decoration: none ; color: #685565; }
-a:hover { text-decoration: underline; color: #986677; }
-
-.navbar
-{
- color : gray;
-}
-
-table
-{
-
-}
-table.navbar
-{
- border : 1px dotted gray;
- background-color: white;
-}
-
-table.navbar p.nomargin
-{
- padding-right : 10pt;
- color : #983759;
-}
-
-table.navbar th.navselect
-{
- background-color: gray;
- color : white;
- font-weight : bold;
-}
-
-font
-{
- font-size : 8pt;
- color : gray;
-}
-
-hr
-{
- border : 1px;
-}
-
-/* Module summary page */
-
-li
-{
- margin-top : 1em;
-}
-
-li i
-{
- display : block;
- clear : right;
- font-style : normal;
-}
-
-/* Class summary page */
-
-h2.class, code
-{
- color : #983759;
-}
-
-h2.class
-{
- display : block;
- margin : 0px;
- text-align : center;
- margin-top : 3em;
- margin-bottom : 2em;
-}
-
-table.summary, table.details, table.func-details
-{
- border : 0px;
- background-color: white;
-}
-
-table.summary tr, table.summary td, table.summary th,
-table.details tr, table.details td, table.details th
-{
- background-color: white;
- border : 0px;
-}
-
-table.summary td
-{
- background-color: #EEEEEE;
- padding : 5pt;
-}
-
-table.func-details h3
-{
- display : block;
- background-color: #EEEEEE;
- font-family : monospace;
- font-size : 8pt;
- font-weight : normal;
- color : #983759;
- padding : 5pt;
-}
-
-/* Index page */
-
-table.index
-{
- border : 0px;
- background-color: white;
-}
-
-table.index tr, table.index td, table.index th
-{
- background-color: white;
- border : 0px;
-}
-
-table.index th
-{
- font-size : 12pt;
- padding-top : 2em;
- padding-bottom : 2em;
-}
-
-table.index td
-{
- padding-left : 20pt;
-}
-
View
26 Sources/sink/linking.py
@@ -7,12 +7,12 @@
# License : BSD License (revised)
# -----------------------------------------------------------------------------
# Creation date : 23-Jul-2007
-# Last mod. : 20-Sep-2007
+# Last mod. : 29-Sep-2009
# -----------------------------------------------------------------------------
# TODO: Make it standalone (so it can be intergrated into Mercurial Contrib)
-import os, sys, sha, stat, getopt, shutil
+import os, sys, hashlib, stat, getopt, shutil
#------------------------------------------------------------------------------
#
@@ -227,13 +227,13 @@ def __str__( self ):
Available operations are:
- link init Initializes a link database for a specific folder
- link add Creates a link between two file
- link remove Removes a link between two files
- link status Gives the status of linked files
- link update Updates the linked files
+ -l init Initializes a link database for a specific folder
+ -l add Creates a link between two file
+ -l remove Removes a link between two files
+ -l status Gives the status of linked files
+ -l update Updates the linked files
- sink link init [PATH]
+ sink -l init [PATH]
Initialises the link database for the current folder, or the folder at the
given PATH. If PATH is omitted, it will use the current folder, or will look
@@ -242,7 +242,7 @@ def __str__( self ):
There are no options for this command.
- sink link add [OPTIONS] SOURCE DESTINATION
+ sink -l add [OPTIONS] SOURCE DESTINATION
Creates a link from the the SOURCE to the DESTINATION. The DESTINATION must
be contained in a directory where the 'link init' command was run.
@@ -251,14 +251,14 @@ def __str__( self ):
-w, --writable Link will be made writable (so that you can update it)
- sink link status [PATH|LINK]...
+ sink -l status [PATH|LINK]...
Returns the status of the given links. If no link is given, the status of
all links will be returned. When no argument is given, the current
directory (or one of its parent) must contain a link database, otherwise
you should give a PATH containing a link databae.
- sink link update [OPTIONS] [PATH|LINK]...
+ sink -l update [OPTIONS] [PATH|LINK]...
Updates the given links in the current or given PATH, or updates only the
given list of LINKs (they must belong to the same link DB, accessible from
@@ -275,7 +275,7 @@ def __str__( self ):
-f, --force Forces the update, ignoring local modifications
-m, --merge Uses the $MERGETOOL to merge the link source and dest
- sink link remove LINK [LINK..]
+ sink -l remove LINK [LINK..]
Removes one or more link from the link database. The links destinations
won't be removed from the filesystem unlesse you specify '--delete'.
@@ -535,7 +535,7 @@ def _readLocal( self, path ):
return c
def _sha( self, content ):
- return sha.new(content).hexdigest()
+ return hashlib.sha1(content).hexdigest()
def _mtime( self, path ):
"""Returns the modification time of the file at the give path."""
View
57 Sources/sink/main.py
@@ -1,13 +1,12 @@
#!/usr/bin/env python
-# Encoding: iso-8859-1
# -----------------------------------------------------------------------------
-# Project : Sink <http://sofware.type-z.org/sink>
+# Project : Sink <http://github.com/sebastien/sink>
# -----------------------------------------------------------------------------
# Author : Sebastien Pierre <sebastien@type-z.org>
# License : BSD License (revised)
# -----------------------------------------------------------------------------
# Creation date : 03-Dec-2004
-# Last mod. : 24-Jul-2006
+# Last mod. : 29-Sep-2009
# -----------------------------------------------------------------------------
import os, sys, shutil, getopt, string, ConfigParser
@@ -21,7 +20,7 @@
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
from sink import tracking, linking
-__version__ = "0.9.8"
+__version__ = "1.0.0"
#------------------------------------------------------------------------------
#
@@ -35,7 +34,7 @@ class Logger:
@staticmethod
def default():
- """Returnts the default logger instance."""
+ """Returs the default logger instance."""
if not hasattr(Logger, "DEFAULT"):
Logger.DEFAULT = Logger()
return Logger.DEFAULT
@@ -71,24 +70,30 @@ def _write( self, stream, *a ):
#------------------------------------------------------------------------------
USAGE = """\
-Sink - v.%s
+sink (%s)
- Sink is the swiss army-knife for many common development synchronisation
- needs.
+Sink is the swiss army-knife for many common directory comparison and
+synchronization.
- Usage: sink [OPERATION?] [ARGUMENTS]
+Usage: sink [MODE] [OPTIONS]
- OPERATION the operation you want to use ('changes' by default, see below)
- ARGUMENTS the arguments to the operations (use --help to know more)
+Modes:
- Operations:
+ (-d/--diff) Lists the changes between two or more directories
+ (-l/--link)
+ (-h/--help) Gives detailed help about a specific operation
- changes (DEF) Lists the changes between two or more directories
- link Manage synchrnoisation links between files
- help Gives detailed help about a specific operation
+Options:
- Type 'sink help changes' or 'sink changes --help' to get detailed information
- about the 'changes' operation.
+ See 'sink --help changes' and 'sink --help link' for more information
+ about each mode options.
+
+Examples:
+
+ sink DIR1 DIR2 DIR3 Compares the contents of DIR1, DIR2 and DIR3
+ sink -n1 DIR1 DIR2 Shows difference between version of file 1
+ in the listing given by 'sink DIR1 DIR2'
+ sink --only '*.py' D1 D2 Comparens the '*.py' files in D1 and D2
""" % (__version__)
@@ -101,8 +106,8 @@ def _write( self, stream, *a ):
}
OPERATIONS = {
- "list":tracking.Engine,
- "link":linking.Engine,
+ "-c":tracking.Engine,
+ "-l":linking.Engine,
"":tracking.Engine
}
@@ -159,12 +164,22 @@ def run( arguments, runningPath=".", logger=None ):
# If there is no arguments
args = arguments
if not args or args[0] in ('-h', '--help'):
- print USAGE
- return
+ if len(args) == 2:
+ if args[1] == "diff":
+ print tracking.USAGE
+ elif args[1] == "link":
+ print linking.USAGE
+ else:
+ print USAGE
+ return
+ else:
+ print USAGE
+ return
elif args[0] == '--version':
print __version__
return
elif args[0] in OPERATIONS.keys():
+ print "*****", args
engine = OPERATIONS[args[0]](logger, config)
return engine.run(args[1:])
#try:
View
119 Sources/sink/tracking.py
@@ -4,18 +4,19 @@
# Project : Sink
# -----------------------------------------------------------------------------
# Author : Sebastien Pierre <sebastien@type-z.org>
+# -----------------------------------------------------------------------------
# License : BSD License (revised)
# -----------------------------------------------------------------------------
# Creation date : 09-Dec-2003
-# Last mod. : 26-Mar-2008
+# Last mod. : 29-Sep-2009
# -----------------------------------------------------------------------------
# Notes : NodeStates SHOULD not be created directly, because they
# MUST be cached (signature and location) in their
# containing state to be processable by the change
# tracker.
# -----------------------------------------------------------------------------
-import os, sha, stat, time, fnmatch, getopt
+import os, hashlib, stat, time, fnmatch, getopt, simplejson, uuid
# Error messages
@@ -40,6 +41,7 @@ class NodeState:
ADDED = "+"
REMOVED = "-"
MODIFIED = "m"
+ COUNTER = 0
def __init__( self, state, location, usesSignature=True, accepts=(),
rejects=() ):
@@ -53,6 +55,7 @@ def __init__( self, state, location, usesSignature=True, accepts=(),
attributes attributes from the local filesystem, but this implies that the
nodes exists on the file system. Otherwise the `_attributes' and `_contentSignature'
attributes can be set by hand."""
+ self._uid = "N%s" % (NodeState.COUNTER)
self._parent = None
self._accepts = accepts
self._rejects = rejects
@@ -62,11 +65,35 @@ def __init__( self, state, location, usesSignature=True, accepts=(),
self._attributesSignature = None
self._usesSignature = usesSignature
self._belongsToState( state )
+ NodeState.COUNTER += 1
self.location( location )
assert type(self._accepts) in (tuple, list)
assert type(self._rejects) in (tuple, list)
state.onNodeCreated(self)
+ def exportToDict( self ):
+ result = {
+ "parent":self._parent and self._parent.location(),
+ "type": self.__class__.__name__,
+ "uid": self._uid,
+ "accepts":self._accepts,
+ "rejects":self._rejects,
+ "attributes":self._attributes,
+ "tags":self._tags,
+ "contentSignature":self._contentSignature,
+ "attributesSignature":self._usesSignature,
+ }
+ return result
+
+ def importFromDict( self, data ):
+ self._accepts = data["accepts"]
+ self._rejects = data["rejects"]
+ self._attributes = data["attributes"]
+ self._tags = data["tags"]
+ self._contentSignature = data["contentSignature"]
+ self._attributesSignature = data["attributesSignature"]
+ return self
+
def isDirectory( self ):
"""Tells wether the node is a directory or not."""
return False
@@ -210,12 +237,12 @@ def _updateSignature( self ):
# Updates the attributes signature
items = self._attributes.items()
items.sort()
- signature = sha.new()
+ signature = []
for key, value in items:
# Creation attribute does not appear in the attributes signature
if self._attributeInSignature(key):
- signature.update(str(key)+str(value))
- self._attributesSignature = signature.hexdigest()
+ signature.append(str(key)+str(value))
+ self._attributesSignature = hashlib.sha1("".join(signature)).hexdigest()
def getContentSignature( self ):
if self._contentSignature == None: self._updateSignature()
@@ -255,6 +282,23 @@ def __init__( self, state, location, accepts=(), rejects=() ):
NodeState.__init__(self, state, location, usesSignature=False,
accepts=accepts, rejects=rejects )
+ def exportToDict( self ):
+ result = NodeState.exportToDict(self)
+ result["children"] = tuple(n.exportToDict() for n in self._children)
+ return result
+
+ def importFromDict( self, data ):
+ result = NodeState.importFromDict(self, data)
+ for child in data["children"]:
+ child_type = child["type"]
+ if child_type == DirectoryNodeState.__name__:
+ assert None
+ elif child_type == NodeState.__name__:
+ assert None
+ else:
+ assert None, "Unsuported child type"
+ return self
+
def isDirectory( self ):
"""Returns True."""
return True
@@ -375,10 +419,10 @@ def _updateSignature( self ):
"""A directory signature is the signature of the string composed of the
names of all of its elements."""
NodeState._updateSignature(self)
- self._contentSignature = sha.new()
+ children = []
for child in self.getChildren():
- self._contentSignature.update(os.path.basename(child.location()))
- self._contentSignature = self._contentSignature.hexdigest()
+ children.append(os.path.basename(child.location()))
+ self._contentSignature = hashlib.sha1("".join(children)).hexdigest()
def __repr__(self):
def indent(text, value = " ", firstLine=False):
@@ -427,8 +471,7 @@ def _updateSignature( self ):
# allows to perform quick changes detection when large files are
# involved.
# if self.usesSignature():
- self._contentSignature = sha.new(self.getData())
- self._contentSignature = self._contentSignature.hexdigest()
+ self._contentSignature = hashlib.sha1(self.getData()).hexdigest()
#------------------------------------------------------------------------------
#
@@ -538,6 +581,18 @@ def __init__( self, rootLocation, rootNodeState=None, populate=False,
if populate:
self.populate()
+ def exportToDict( self ):
+ locations = {}
+ for k,v in self._locations.items():
+ locations[k] = v._uid
+ result = {
+ "accepts":self._accepts,
+ "rejects":self._rejects,
+ "locations":locations,
+ "rootNodeState":self._rootNodeState and self._rootNodeState.exportToDict()
+ }
+ return result
+
def onNodeCreated( self, node ):
"""A callback placeholder that can be used to output stuff when a node
is created."""
@@ -638,7 +693,7 @@ def nodesByContentSignature( self ):
def __repr__(self):
return repr(self.root())
-
+
#------------------------------------------------------------------------------
#
# Change tracking
@@ -857,7 +912,7 @@ def onRemoved(self, node):
#TODO: Describe -d option
USAGE = """\
- sink compare [OPTIONS] [OPERATION] ORIGIN COMPARED...
+ sink [-d] [OPTIONS] [OPERATION] ORIGIN COMPARED...
ORIGIN is the directory to which we want to compare the others
COMPARED is a list of directories that will be compared to ORIGIN
@@ -866,37 +921,41 @@ def onRemoved(self, node):
-c, --content (DEF) Uses content analysis to detect changes
-t, --time Uses timestamp to detect changes
+ -nNUM Compares the file at line NUM in the listing
--ignore-spaces Ignores the spaces when analyzing the content
--ignore GLOBS Ignores the files that match the glob
--only GLOBS Only accepts the file that match glob
- --filter PATTERN Tells which files you want to list
+ --difftool TOOL Specifies a specific too for the -n option
You can also specify what you want to be listed in the diff:
+ [-+]A Hides/Shows ALL files [=]
[-+]s Hides/Shows SAME files [=]
[-+]a Hides/Shows ADDED files [+]
- [-+]r Hides/Shows REMOVED files !
+ [-+]r Hides/Shows REMOVED files [-]
[-+]m Hides/Shows MODIFIED files [>] or [<]
- [-+]n Hides/Shows NEWER files [>]
+ [-+]N Hides/Shows NEWER files [>]
[-+]o Hides/Shows OLDER files [<]
- PATTERN is a string containing any of the symbols between braces ([?])
- listed above (eg. --filter '+-<>').
-
GLOBS understand '*' and '?', will refer to the basename and can be
separated by commas. If a directory matches the glob, it will not be
traversed (ex: --ignore '*.pyc,*.bak,.[a-z]*')
+ Legend:
+
+ [=] no changes [+] file added [>] changed/newer
+ [-] file removed [<] changed/older
+ ! file missing
"""
CONTENT_MODE = True
TIME_MODE = False
ADDED = "[+]"
-REMOVED = " ! "
+REMOVED = "[-]"
NEWER = "[>]"
OLDER = "[<]"
SAME = "[=]"
-ABSENT = " "
+ABSENT = " ! "
class Engine:
"""Implements operations used by the Sink main command-line interface."""
@@ -933,11 +992,11 @@ def run( self, arguments ):
command, arguments = arguments[0], arguments[1:]
# We extract the arguments
try:
- optlist, args = getopt.getopt( arguments, "cthVvld:iarsmno",\
+ optlist, args = getopt.getopt( arguments, "cthVvln:iarsmNo",\
["version", "help", "verbose", "list", "checkin", "checkout",
"modified",
"time", "content", "ignore-spaces", "ignorespaces", "diff=", "ignore=",
- "ignores=", "accept=", "accepts=", "only="])
+ "ignores=", "accept=", "accepts=", "filter", "only="])
except Exception, e:
return self.logger.error(e)
# We parse the options
@@ -961,8 +1020,11 @@ def run( self, arguments ):
if arg.find(":") == -1: diff, _dir = int(arg), 0
else: diff, _dir = map(int, arg.split(":"))
self.diffs.append((diff, _dir))
- elif opt == '--diff':
+ elif opt == '--difftool':
self.diff_command = arg
+ elif opt == "+A":
+ for t in [ADDED, REMOVED, SAME, NEWER, OLDER]:
+ self.show[t] = False
elif opt in ('-a'):
self.show[ADDED] = False
elif opt in ('-r'):
@@ -979,7 +1041,10 @@ def run( self, arguments ):
# We adjust the show
nargs = []
for arg in args:
- if arg == "+a":
+ if arg == "+A":
+ for t in [ADDED, REMOVED, SAME, NEWER, OLDER]:
+ self.show[t] = True
+ elif arg == "+a":
self.show[ADDED] = True
elif arg == "+r":
self.show[REMOVED] = True
@@ -1038,6 +1103,12 @@ def run( self, arguments ):
method=Tracker.TIME))
any_changes = changes[-1].anyChanges() or any_changes
+ print "==================="
+ f = file("pouet.data", "w")
+ import simplejson
+ f.write((simplejson.dumps(origin_state.exportToDict())))
+ print "==================="
+ assert None
# We apply the operation
if any_changes:
self.listChanges(
View
30 TODO
@@ -1,5 +1,35 @@
== TODO
+cherry:~...Staging/spherechat >> sink ../../Distribution .
+00 [+] .gitignore
+01 [+] backup
+02 [=][>] demo.conf
+03 [=][>] demo.html
+04 [=] ! spherechat.html
+05 [=][>] spherechat.zip
+06 [+] start-server
+07 [+] stop-server
+08 [+] backups/.keep
+09 [=][>] lib/js/spherechat-bundle.js
+10 [=][>] lib/js/spherechat.js
+
+cherry:~...Staging/spherechat >> sink . ../../Distribution
+00 [=] ! .gitignore
+01 [=] ! backup
+02 [=][<] demo.conf
+03 [=][<] demo.html
+04 [+] spherechat.html
+05 [=][<] spherechat.zip
+06 [=] ! start-server
+07 [=] ! stop-server
+08 [=] ! backups/.keep
+09 [=][<] lib/js/spherechat-bundle.js
+10 [=][<] lib/js/spherechat.js
+
+# I don't get the meaning of [=] here, and besides I'm pretty sure that the files
+# are the same, except for lib/js/*
+
+========
- Add the possibility to track changes between more than one directory
- Make benchmarks

0 comments on commit 78df128

Please sign in to comment.
Something went wrong with that request. Please try again.