Compare commits
	
		
			18 Commits
		
	
	
		
			feature.co
			...
			feature.we
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					cdb8dbafe2 | ||
| 
						 | 
					33b22f95ab | ||
| 
						 | 
					39f77886db | ||
| 
						 | 
					f7470e4a7a | ||
| 
						 | 
					c10c002ee3 | ||
| 
						 | 
					a47ec0df0a | ||
| 
						 | 
					be95050a4b | ||
| 
						 | 
					5043c4e845 | ||
| 
						 | 
					dacbf0707b | ||
| 
						 | 
					34c9d04a35 | ||
| 
						 | 
					aa2868eb17 | ||
| 
						 | 
					a2c832a130 | ||
| 
						 | 
					b17de014ec | ||
| 
						 | 
					b1b72190e1 | ||
| 
						 | 
					2ff2f888bc | ||
| 
						 | 
					57c2389aae | ||
| 
						 | 
					d54c86f813 | ||
| 
						 | 
					05a9697007 | 
@@ -13,10 +13,11 @@ or newer to properly run this version of Indefero!
 | 
			
		||||
- Display monotone file and directory attributes in the tree and file view
 | 
			
		||||
  (needs a monotone with an interface version of 13.1 or newer)
 | 
			
		||||
- The context area is now kept in view when a page scrolls down several pages
 | 
			
		||||
- git repositories are now available under /r/projectname/
 | 
			
		||||
 | 
			
		||||
## Bugfixes
 | 
			
		||||
 | 
			
		||||
- The SVN interface acts more robust if an underlying repository has been restructured (issue 364)
 | 
			
		||||
- The SVN interface acts more robust if an underlying repository has been restructured (issues 364 and 721)
 | 
			
		||||
- monotone zip archive entries now all carry the revision date as mtime (issue 645)
 | 
			
		||||
- Timeline only displays filter options for items a user has actually access to (issue 655)
 | 
			
		||||
- The log, tags and branches parsers for Mercurial are more robust now (issue 663)
 | 
			
		||||
@@ -29,12 +30,16 @@ or newer to properly run this version of Indefero!
 | 
			
		||||
- Prevent a timeout from popping up when Usher is restarted (issue 695)
 | 
			
		||||
- The SyncMonotone plugin now cleans up partial artifacts it created during the addition of
 | 
			
		||||
  a new project or monotone key, in case an error popped up in the middle (issue 697)
 | 
			
		||||
- Indefero now sends the MD5 checksum as HTTP header when downloading a file from the
 | 
			
		||||
  download area. Additionally, a unneeded redirect has been removed. (issue 716)
 | 
			
		||||
- Better error detection and reporting in the SyncMonotone plugin
 | 
			
		||||
  ATTENTION: This needs Pluf 46b7f251 or newer!
 | 
			
		||||
- Fix the branch links users of the Subversion frontend get when they enter a wrong revision
 | 
			
		||||
  and only display this list if there are any branches available for all SCMs
 | 
			
		||||
- If git's author name is not encoded in an UTF-8 compatible encoding, skip the author lookup,
 | 
			
		||||
  as we have no information what the author string is actually encoded in
 | 
			
		||||
- Indefero no longer displays an empty parents paragraph in the commit view for root revisions of
 | 
			
		||||
  a git repository
 | 
			
		||||
 | 
			
		||||
## Documentation
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										57
									
								
								doc/httprepos-git.mdtext
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								doc/httprepos-git.mdtext
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
			
		||||
# Accessing git repositories over HTTP
 | 
			
		||||
 | 
			
		||||
Starting with indefero 1.2, git repositories are provided via http,
 | 
			
		||||
featuring read-only and read-write access with full integration with
 | 
			
		||||
indefero's access control mechanisms.
 | 
			
		||||
 | 
			
		||||
## Access git repositories
 | 
			
		||||
 | 
			
		||||
The repositories are available under http://YOURHOST/BASEPATH/r/PROJECT.
 | 
			
		||||
 | 
			
		||||
For authentication, use the "extra password" which you can find on your
 | 
			
		||||
profile page.
 | 
			
		||||
 | 
			
		||||
## Setup
 | 
			
		||||
 | 
			
		||||
The main thing to setup is git_repositories and git_remote_url in
 | 
			
		||||
src/IDF/conf/idf.php
 | 
			
		||||
 | 
			
		||||
git_remote_url should match http://YOURHOST/BASEPATH/r/%s, while
 | 
			
		||||
git_repositories points to the local directory supposed to contain the
 | 
			
		||||
repositories like /PATH/TO/REPOSITORIES/%s.git.
 | 
			
		||||
 | 
			
		||||
## Setup requirements when using PHP over FastCGI
 | 
			
		||||
 | 
			
		||||
There are a couple of things to setup when using PHP over FastCGI, as compared
 | 
			
		||||
to mod_php or similar integrated mechanisms:
 | 
			
		||||
 | 
			
		||||
- You will need to setup a RewriteRule for mod_rewrite (in case of Apache,
 | 
			
		||||
  analogous mechanisms might need to be setup for other http daemons), which
 | 
			
		||||
  passes through the Authorization HTTP Header of a request.
 | 
			
		||||
 | 
			
		||||
  In case of mod_rewrite, the necessary line is
 | 
			
		||||
  one of (depending on server configuration):
 | 
			
		||||
      RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
 | 
			
		||||
      RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization},L]
 | 
			
		||||
 | 
			
		||||
- The FastCGI adaptor must allow large requests to be handled by PHP,
 | 
			
		||||
  otherwise push might fail.
 | 
			
		||||
 | 
			
		||||
  For mod_fcgid, this is the FcgidMaxRequestLen option, or MaxRequestLen in
 | 
			
		||||
  older versions. Set this option to some large enough value - there is no
 | 
			
		||||
  issue with RAM use, as another option defines the limit after which the
 | 
			
		||||
  request is backed on disk, which can remain small.
 | 
			
		||||
 | 
			
		||||
## When migrating from syncgit
 | 
			
		||||
 | 
			
		||||
HTTP access can be used in parallel to syncgit.
 | 
			
		||||
 | 
			
		||||
If you want to disable syncgit, just undo the changes detailled
 | 
			
		||||
in doc/syncgit.mdtext:
 | 
			
		||||
- In src/IDF/conf/idf.php keep git_repositories
 | 
			
		||||
- In src/IDF/conf/idf.php adapt git_remote_url to http://host/basepath/r/%s 
 | 
			
		||||
- In src/IDF/conf/idf.php remove idf_plugin_syncgit*
 | 
			
		||||
- Remove the cronjob that called gitcron.php
 | 
			
		||||
- Disable the git daemon (eg. /etc/event.d/local-git-daemon)
 | 
			
		||||
- You can remove the git user and group, if you also adapt the git repositories
 | 
			
		||||
  to be owned by the www user.
 | 
			
		||||
@@ -44,7 +44,7 @@ class IDF_Middleware
 | 
			
		||||
    function process_request(&$request)
 | 
			
		||||
    {
 | 
			
		||||
        $match = array();
 | 
			
		||||
        if (preg_match('#^/(?:api/p|p)/([\-\w]+)/?#', $request->query, $match)) {
 | 
			
		||||
        if (preg_match('#^/(?:api/p|p|r)/([\-\w]+)/?#', $request->query, $match)) {
 | 
			
		||||
            try {
 | 
			
		||||
                $request->project = IDF_Project::getOr404($match[1]);
 | 
			
		||||
            } catch (Pluf_HTTP_Error404 $e) {
 | 
			
		||||
 
 | 
			
		||||
@@ -497,5 +497,10 @@ class IDF_Scm
 | 
			
		||||
    {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function repository($request, $match)
 | 
			
		||||
    {
 | 
			
		||||
        throw new Exception('This repository does not support web based repository access');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -646,7 +646,11 @@ class IDF_Scm_Git extends IDF_Scm
 | 
			
		||||
        $c['full_message'] = IDF_Commit::toUTF8($c['full_message']);
 | 
			
		||||
        $c['title'] = IDF_Commit::toUTF8($c['title']);
 | 
			
		||||
        if (isset($c['parents'])) {
 | 
			
		||||
            $c['parents'] = explode(' ', trim($c['parents']));
 | 
			
		||||
            $c['parents'] = preg_split('/ /', trim($c['parents']), -1, PREG_SPLIT_NO_EMPTY);
 | 
			
		||||
        } else {
 | 
			
		||||
            // this is actually an error state because we should _always_
 | 
			
		||||
            // be able to parse the parents line with every git version
 | 
			
		||||
            $c['parents'] = null;
 | 
			
		||||
        }
 | 
			
		||||
        $res[] = (object) $c;
 | 
			
		||||
        return $res;
 | 
			
		||||
@@ -920,4 +924,113 @@ class IDF_Scm_Git extends IDF_Scm
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function repository($request, $match)
 | 
			
		||||
    {
 | 
			
		||||
        // handle a couple of workarounds for authenticating with FastCGI/PHP
 | 
			
		||||
        if (!empty($_SERVER['HTTP_AUTHORIZATION']))
 | 
			
		||||
            list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = explode(':' , base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));
 | 
			
		||||
        elseif (!empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION']))
 | 
			
		||||
            list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = explode(':' , base64_decode(substr($_SERVER['REDIRECT_HTTP_AUTHORIZATION'], 6)));
 | 
			
		||||
        elseif (!empty($_SERVER['REDIRECT_REMOTE_USER']))
 | 
			
		||||
            list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = explode(':' , base64_decode(substr($_SERVER['REDIRECT_REMOTE_USER'], 6)));
 | 
			
		||||
 | 
			
		||||
        // authenticate: authenticate connection through "extra" password
 | 
			
		||||
        if (!empty($_SERVER['PHP_AUTH_USER'])) {
 | 
			
		||||
            $sql = new Pluf_SQL('login=%s', array($_SERVER['PHP_AUTH_USER']));
 | 
			
		||||
            $users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen()));
 | 
			
		||||
            if (count($users) == 1 && $users[0]->active) {
 | 
			
		||||
                $user = $users[0];
 | 
			
		||||
                $realkey = substr(sha1($user->password.Pluf::f('secret_key')), 0, 8);
 | 
			
		||||
                if ($_SERVER['PHP_AUTH_PW'] == $realkey) {
 | 
			
		||||
                    $request->user = $user;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (IDF_Precondition::accessSource($request) !== true) {
 | 
			
		||||
            $response = new Pluf_HTTP_Response("");
 | 
			
		||||
            $response->status_code = 401;
 | 
			
		||||
            $response->headers['WWW-Authenticate']='Basic realm="git for '.$this->project.'"';
 | 
			
		||||
            return $response;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $path = $match[2];
 | 
			
		||||
 
 | 
			
		||||
        if (!file_exists($this->repo)) {
 | 
			
		||||
            mkdir($this->repo, 0750, true);
 | 
			
		||||
            $out = array();
 | 
			
		||||
            $res = 0;
 | 
			
		||||
            exec(sprintf(Pluf::f('idf_exec_cmd_prefix', '').
 | 
			
		||||
                         Pluf::f('git_path', 'git').' --git-dir=%s init', escapeshellarg($this->repo)),
 | 
			
		||||
                 $out, $res);
 | 
			
		||||
            if ($res != 0) {
 | 
			
		||||
                Pluf_Log::error(array('IDF_Scm_Git::repository', $res, $this->repo));
 | 
			
		||||
                throw new Exception(sprintf('Init repository error, exit status %d.', $res));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // update files before delivering them
 | 
			
		||||
        if (($path == 'objects/info/pack') || ($path == 'info/refs')) {
 | 
			
		||||
            $cmd = sprintf(Pluf::f('idf_exec_cmd_prefix', '').
 | 
			
		||||
                       'GIT_DIR=%s '.Pluf::f('git_path', 'git').' update-server-info -f',
 | 
			
		||||
                       escapeshellarg($this->repo));
 | 
			
		||||
            self::shell_exec('IDF_Scm_Git::repository', $cmd);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // smart HTTP discovery
 | 
			
		||||
        if ($path == 'info/refs' && !empty($request->GET['service'])){
 | 
			
		||||
            $service = $request->GET['service'];
 | 
			
		||||
            switch ($service) {
 | 
			
		||||
            case 'git-receive-pack':
 | 
			
		||||
                if (IDF_Precondition::projectMemberOrOwner($request) !== true) {
 | 
			
		||||
                    $response = new Pluf_HTTP_Response("");
 | 
			
		||||
                    $response->status_code = 401;
 | 
			
		||||
                    $response->headers['WWW-Authenticate']='Basic realm="git for '.$this->project.'"';
 | 
			
		||||
                    return $response;
 | 
			
		||||
                }
 | 
			
		||||
            case 'git-upload-pack':
 | 
			
		||||
                $content = sprintf('%04x',strlen($service)+15).
 | 
			
		||||
                         '# service='.$service."\n0000";
 | 
			
		||||
                $content .= self::shell_exec('IDF_Scm_Git::repository',
 | 
			
		||||
                         Pluf::f('idf_exec_cmd_prefix', '').
 | 
			
		||||
                         $service.' --stateless-rpc --advertise-refs '.
 | 
			
		||||
                         $this->repo);
 | 
			
		||||
                $response = new Pluf_HTTP_Response($content,
 | 
			
		||||
                         'application/x-'.$service.'-advertisement');
 | 
			
		||||
                return $response;
 | 
			
		||||
            default:
 | 
			
		||||
                throw new Exception('unknown service: '.$service);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        switch($path) {
 | 
			
		||||
        // smart HTTP RPC
 | 
			
		||||
        case 'git-receive-pack':
 | 
			
		||||
                if (IDF_Precondition::projectMemberOrOwner($request) !== true) {
 | 
			
		||||
                    $response = new Pluf_HTTP_Response("");
 | 
			
		||||
                    $response->status_code = 401;
 | 
			
		||||
                    $response->headers['WWW-Authenticate']='Basic realm="git for '.$this->project.'"';
 | 
			
		||||
                    return $response;
 | 
			
		||||
                }
 | 
			
		||||
        case 'git-upload-pack':
 | 
			
		||||
            $response = new Pluf_HTTP_Response_CommandPassThru(
 | 
			
		||||
                   Pluf::f('idf_exec_cmd_prefix', '').$path.
 | 
			
		||||
                   ' --stateless-rpc '.$this->repo,
 | 
			
		||||
                   'application/x-'.$path.'-result');
 | 
			
		||||
            $response->setStdin(fopen('php://input', 'rb'));
 | 
			
		||||
            return $response;
 | 
			
		||||
 | 
			
		||||
        // regular file
 | 
			
		||||
        default:
 | 
			
		||||
            // make sure we're inside the repo hierarchy (ie. no break-out)
 | 
			
		||||
            if (is_file($this->repo.'/'.$path) &&
 | 
			
		||||
              strpos(realpath($this->repo.'/'.$path), $this->repo.'/') == 0) {
 | 
			
		||||
                return new Pluf_HTTP_Response_File($this->repo.'/'.$path,
 | 
			
		||||
                       'application/octet-stream');
 | 
			
		||||
            } else {
 | 
			
		||||
                return new Pluf_HTTP_Response_NotFound($request);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,6 @@
 | 
			
		||||
 */
 | 
			
		||||
class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    public $username = '';
 | 
			
		||||
    public $password = '';
 | 
			
		||||
    private $assoc = array('dir' => 'tree',
 | 
			
		||||
@@ -48,11 +47,7 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
 | 
			
		||||
    public function isAvailable()
 | 
			
		||||
    {
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' info --no-auth-cache --xml --username=%s --password=%s %s',
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($this->repo));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('info', '--xml'), $this->repo);
 | 
			
		||||
        $xmlInfo = self::shell_exec('IDF_Scm_Svn::isAvailable', $cmd);
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
@@ -163,12 +158,7 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
            return IDF_Scm::REVISION_VALID;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' info --no-auth-cache --username=%s --password=%s --revision=%s %s',
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($rev),
 | 
			
		||||
                       escapeshellarg($this->repo));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('info'), $this->repo, $rev);
 | 
			
		||||
        self::exec('IDF_Scm_Svn::validateRevision', $cmd, $out, $ret);
 | 
			
		||||
 | 
			
		||||
        if ($ret == 0)
 | 
			
		||||
@@ -190,12 +180,9 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Else, test the path on revision
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' info --no-auth-cache --xml --username=%s --password=%s --revision=%s %s',
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($rev),
 | 
			
		||||
                       escapeshellarg($this->repo.'/'.self::smartEncode($path)));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('info', '--xml'),
 | 
			
		||||
                             $this->repo.'/'.self::smartEncode($path),
 | 
			
		||||
                             $rev);
 | 
			
		||||
        $xmlInfo = self::shell_exec('IDF_Scm_Svn::testHash', $cmd);
 | 
			
		||||
 | 
			
		||||
        // If exception is thrown, return false
 | 
			
		||||
@@ -217,12 +204,9 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
 | 
			
		||||
    public function getTree($commit, $folder='/', $branch=null)
 | 
			
		||||
    {
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' ls --no-auth-cache --xml --username=%s --password=%s --revision=%s %s',
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($commit),
 | 
			
		||||
                       escapeshellarg($this->repo.'/'.self::smartEncode($folder)));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('ls', '--xml'),
 | 
			
		||||
                             $this->repo.'/'.self::smartEncode($folder),
 | 
			
		||||
                             $commit);
 | 
			
		||||
        $xml = simplexml_load_string(self::shell_exec('IDF_Scm_Svn::getTree', $cmd));
 | 
			
		||||
        $res = array();
 | 
			
		||||
        $folder = (strlen($folder) and ($folder != '/')) ? $folder.'/' : '';
 | 
			
		||||
@@ -258,12 +242,7 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
        if (isset($this->cache['commitmess'][$rev])) {
 | 
			
		||||
            return $this->cache['commitmess'][$rev];
 | 
			
		||||
        }
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' log --no-auth-cache --xml --limit 1 --username=%s --password=%s --revision=%s %s',
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($rev),
 | 
			
		||||
                       escapeshellarg($this->repo));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('log', '--xml', '--limit', '1'), $this->repo, $rev);
 | 
			
		||||
        try {
 | 
			
		||||
            $xml = simplexml_load_string(self::shell_exec('IDF_Scm_Svn::getCommitMessage', $cmd));
 | 
			
		||||
            $this->cache['commitmess'][$rev] = (string) $xml->logentry->msg;
 | 
			
		||||
@@ -279,12 +258,8 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
        if ($rev == null) {
 | 
			
		||||
            $rev = 'HEAD';
 | 
			
		||||
        }
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' info --no-auth-cache --xml --username=%s --password=%s --revision=%s %s',
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($rev),
 | 
			
		||||
                       escapeshellarg($this->repo.'/'.self::smartEncode($filename)));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('info', '--xml'),
 | 
			
		||||
                             $this->repo.'/'.self::smartEncode($filename), $rev);
 | 
			
		||||
        $xml = simplexml_load_string(self::shell_exec('IDF_Scm_Svn::getPathInfo', $cmd));
 | 
			
		||||
        if (!isset($xml->entry)) {
 | 
			
		||||
            return false;
 | 
			
		||||
@@ -306,12 +281,9 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
 | 
			
		||||
    public function getFile($def, $cmd_only=false)
 | 
			
		||||
    {
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' cat --no-auth-cache --username=%s --password=%s --revision=%s %s',
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($def->rev),
 | 
			
		||||
                       escapeshellarg($this->repo.'/'.self::smartEncode($def->fullpath)));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('cat'),
 | 
			
		||||
                             $this->repo.'/'.self::smartEncode($def->fullpath),
 | 
			
		||||
                             $def->rev);
 | 
			
		||||
        return ($cmd_only) ?
 | 
			
		||||
            $cmd : self::shell_exec('IDF_Scm_Svn::getFile', $cmd);
 | 
			
		||||
    }
 | 
			
		||||
@@ -327,11 +299,7 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
            return $this->cache['branches'];
 | 
			
		||||
        }
 | 
			
		||||
        $res = array();
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' ls --no-auth-cache --username=%s --password=%s --revision=HEAD %s',
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($this->repo.'/branches'));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('ls'), $this->repo.'/branches', 'HEAD');
 | 
			
		||||
        self::exec('IDF_Scm_Svn::getBranches', $cmd, $out, $ret);
 | 
			
		||||
        if ($ret == 0) {
 | 
			
		||||
            foreach ($out as $entry) {
 | 
			
		||||
@@ -342,11 +310,8 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        ksort($res);
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' info --no-auth-cache --username=%s --password=%s --revision=HEAD %s',
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($this->repo.'/trunk'));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
 | 
			
		||||
        $cmd = $this->svnCmd(array('info'), $this->repo.'/trunk', 'HEAD');
 | 
			
		||||
        self::exec('IDF_Scm_Svn::getBranches', $cmd, $out, $ret);
 | 
			
		||||
        if ($ret == 0) {
 | 
			
		||||
            $res = array('trunk' => 'trunk') + $res;
 | 
			
		||||
@@ -366,11 +331,7 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
            return $this->cache['tags'];
 | 
			
		||||
        }
 | 
			
		||||
        $res = array();
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' ls --no-auth-cache --username=%s --password=%s --revision=HEAD %s',
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($this->repo.'/tags'));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('ls'), $this->repo.'/tags', 'HEAD');
 | 
			
		||||
        self::exec('IDF_Scm_Svn::getTags', $cmd, $out, $ret);
 | 
			
		||||
        if ($ret == 0) {
 | 
			
		||||
            foreach ($out as $entry) {
 | 
			
		||||
@@ -423,12 +384,8 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        $res = array();
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' log --no-auth-cache --xml --limit 1 -v --username=%s --password=%s --revision=%s %s',
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($commit),
 | 
			
		||||
                       escapeshellarg($this->repo));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('log', '--xml', '--limit', '1', '-v'),
 | 
			
		||||
                             $this->repo, $commit);
 | 
			
		||||
        $xmlRes = self::shell_exec('IDF_Scm_Svn::getCommit', $cmd);
 | 
			
		||||
        $xml = simplexml_load_string($xmlRes);
 | 
			
		||||
        $res['author'] = (string) $xml->logentry->author;
 | 
			
		||||
@@ -470,12 +427,7 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
    private function getDiff($rev='HEAD')
 | 
			
		||||
    {
 | 
			
		||||
        $res = array();
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' diff --no-auth-cache -c %s --username=%s --password=%s %s',
 | 
			
		||||
                       escapeshellarg($rev),
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($this->repo));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('diff', '-c', $rev), $this->repo);
 | 
			
		||||
        return self::shell_exec('IDF_Scm_Svn::getDiff', $cmd);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -487,13 +439,8 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
        if ($this->validateRevision($commit) != IDF_Scm::REVISION_VALID) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' log --xml -v --no-auth-cache -r %s --username=%s --password=%s %s',
 | 
			
		||||
                       escapeshellarg($commit),
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($this->repo));
 | 
			
		||||
        $cmd = $this->svnCmd(array('log', '--xml', '-v'), $this->repo, $commit);
 | 
			
		||||
        $out = array();
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $out = self::shell_exec('IDF_Scm_Svn::getChanges', $cmd);
 | 
			
		||||
        $xml = simplexml_load_string($out);
 | 
			
		||||
        if (count($xml) == 0) {
 | 
			
		||||
@@ -570,20 +517,15 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
     *
 | 
			
		||||
     * @return array Changes.
 | 
			
		||||
     */
 | 
			
		||||
    public function getChangeLog($branch=null, $n=10)
 | 
			
		||||
    public function getChangeLog($rev=null, $n=10)
 | 
			
		||||
    {
 | 
			
		||||
        if ($branch != 'HEAD' and !preg_match('/^\d+$/', $branch)) {
 | 
			
		||||
        if ($rev != 'HEAD' and !preg_match('/^\d+$/', $rev)) {
 | 
			
		||||
            // we accept only revisions or HEAD
 | 
			
		||||
            $branch = 'HEAD';
 | 
			
		||||
            $rev = 'HEAD';
 | 
			
		||||
        }
 | 
			
		||||
        $res = array();
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' log --no-auth-cache --xml -v --limit %s --username=%s --password=%s --revision=%s %s',
 | 
			
		||||
                       escapeshellarg($n),
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($branch),
 | 
			
		||||
                       escapeshellarg($this->repo));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('log', '--xml', '-v', '--limit', $n),
 | 
			
		||||
                             $this->repo.'@'.$rev);
 | 
			
		||||
        $xmlRes = self::shell_exec('IDF_Scm_Svn::getChangeLog', $cmd);
 | 
			
		||||
        $xml = simplexml_load_string($xmlRes);
 | 
			
		||||
        foreach ($xml->logentry as $entry) {
 | 
			
		||||
@@ -609,12 +551,8 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
    public function getProperties($rev, $path='')
 | 
			
		||||
    {
 | 
			
		||||
        $res = array();
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' proplist --no-auth-cache --xml --username=%s --password=%s --revision=%s %s',
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($rev),
 | 
			
		||||
                       escapeshellarg($this->repo.'/'.self::smartEncode($path)));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('proplist', '--xml'),
 | 
			
		||||
                             $this->repo.'/'.self::smartEncode($path), $rev);
 | 
			
		||||
        $xmlProps = self::shell_exec('IDF_Scm_Svn::getProperties', $cmd);
 | 
			
		||||
        $props = simplexml_load_string($xmlProps);
 | 
			
		||||
 | 
			
		||||
@@ -643,13 +581,8 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
    private function getProperty($property, $rev, $path='')
 | 
			
		||||
    {
 | 
			
		||||
        $res = array();
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' propget --no-auth-cache --xml %s --username=%s --password=%s --revision=%s %s',
 | 
			
		||||
                       escapeshellarg($property),
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($rev),
 | 
			
		||||
                       escapeshellarg($this->repo.'/'.self::smartEncode($path)));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('propget', $property, '--xml'),
 | 
			
		||||
                             $this->repo.'/'.self::smartEncode($path), $rev);
 | 
			
		||||
        $xmlProp = self::shell_exec('IDF_Scm_Svn::getProperty', $cmd);
 | 
			
		||||
        $prop = simplexml_load_string($xmlProp);
 | 
			
		||||
 | 
			
		||||
@@ -666,16 +599,38 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
    public function getLastCommit($rev='HEAD')
 | 
			
		||||
    {
 | 
			
		||||
        $xmlInfo = '';
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' info --no-auth-cache --xml --username=%s --password=%s --revision=%s %s',
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($rev),
 | 
			
		||||
                       escapeshellarg($this->repo));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('info', '--xml'), $this->repo, $rev);
 | 
			
		||||
        $xmlInfo = self::shell_exec('IDF_Scm_Svn::getLastCommit', $cmd);
 | 
			
		||||
 | 
			
		||||
        $xml = simplexml_load_string($xmlInfo);
 | 
			
		||||
        return (string) $xml->entry->commit['revision'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function svnCmd($args = array(), $repoarg = null, $revarg = null)
 | 
			
		||||
    {
 | 
			
		||||
        $cmdline = array();
 | 
			
		||||
        $cmdline[] = Pluf::f('idf_exec_cmd_prefix', '');
 | 
			
		||||
        $cmdline[] = Pluf::f('svn_path', 'svn');
 | 
			
		||||
        $cmdline[] = '--no-auth-cache';
 | 
			
		||||
        $cmdline[] = '--username='.escapeshellarg($this->username);
 | 
			
		||||
        $cmdline[] = '--password='.escapeshellarg($this->password);
 | 
			
		||||
 | 
			
		||||
        foreach ($args as $arg) {
 | 
			
		||||
            $cmdline[] = escapeshellarg($arg);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($repoarg != null) {
 | 
			
		||||
            if ($revarg != null) {
 | 
			
		||||
                $repoarg .= '@'.$revarg;
 | 
			
		||||
            }
 | 
			
		||||
            $cmdline[] = escapeshellarg($repoarg);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($revarg != null) {
 | 
			
		||||
            $cmdline[] = '--revision='.escapeshellarg($revarg);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return implode(' ', $cmdline);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -132,6 +132,12 @@ class IDF_Views_Source
 | 
			
		||||
                                               $request);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function repository($request, $match)
 | 
			
		||||
    {
 | 
			
		||||
        $scm = IDF_Scm::get($request->project);
 | 
			
		||||
        return $scm->repository($request, $match);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public $treeBase_precond = array('IDF_Precondition::accessSource',
 | 
			
		||||
                                     'IDF_Views_Source_Precondition::scmAvailable',
 | 
			
		||||
                                     'IDF_Views_Source_Precondition::revisionValid');
 | 
			
		||||
 
 | 
			
		||||
@@ -245,6 +245,11 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/source/changesrev/$#',
 | 
			
		||||
               'model' => 'IDF_Views_Source_Svn',
 | 
			
		||||
               'method' => 'changelogRev');
 | 
			
		||||
 | 
			
		||||
$ctl[] = array('regex' => '#^/r/([\-\w]+)/(.*)$#',
 | 
			
		||||
               'base' => $base,
 | 
			
		||||
               'model' => 'IDF_Views_Source',
 | 
			
		||||
               'method' => 'repository');
 | 
			
		||||
 | 
			
		||||
// ---------- WIKI -----------------------------------------
 | 
			
		||||
 | 
			
		||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/$#',
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										55
									
								
								test/IDF/Scm/SvnTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								test/IDF/Scm/SvnTest.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2011 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is distributed in the hope that it will be useful,
 | 
			
		||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
# GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
class IDF_Scm_SvnTest extends PHPUnit_Framework_TestCase
 | 
			
		||||
{
 | 
			
		||||
    private $proj = null;
 | 
			
		||||
 | 
			
		||||
    public function setUp()
 | 
			
		||||
    {
 | 
			
		||||
        $this->proj = new IDF_Project();
 | 
			
		||||
        $this->proj->id = 1;
 | 
			
		||||
        $this->proj->name = $this->proj->shortname = 'test';
 | 
			
		||||
        $this->proj->create();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function tearDown()
 | 
			
		||||
    {
 | 
			
		||||
        $this->proj->delete();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function createMock($reponame)
 | 
			
		||||
    {
 | 
			
		||||
        $repourl = 'file://'.DATADIR.'/'.__CLASS__.'/'.$reponame;
 | 
			
		||||
        $instance = new IDF_Scm_Svn($repourl, $this->proj);
 | 
			
		||||
        return $instance;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testAccessHistoryOfRenamedAndDeletedFiles()
 | 
			
		||||
    {
 | 
			
		||||
        $instance = $this->createMock(__FUNCTION__);
 | 
			
		||||
        $this->assertEquals('new-file', $instance->getPathInfo('new-file', 1)->fullpath);
 | 
			
		||||
        $this->assertEquals('alternate-name', $instance->getPathInfo('alternate-name', 2)->fullpath);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
3
 | 
			
		||||
@@ -0,0 +1,2 @@
 | 
			
		||||
4
 | 
			
		||||
layout sharded 1000
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
fsfs
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
0
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
K 8
 | 
			
		||||
svn:date
 | 
			
		||||
V 27
 | 
			
		||||
2011-08-12T17:06:15.429110Z
 | 
			
		||||
END
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
K 10
 | 
			
		||||
svn:author
 | 
			
		||||
V 7
 | 
			
		||||
patrick
 | 
			
		||||
K 8
 | 
			
		||||
svn:date
 | 
			
		||||
V 27
 | 
			
		||||
2011-08-12T17:07:33.111035Z
 | 
			
		||||
K 7
 | 
			
		||||
svn:log
 | 
			
		||||
V 12
 | 
			
		||||
added a file
 | 
			
		||||
END
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
K 10
 | 
			
		||||
svn:author
 | 
			
		||||
V 7
 | 
			
		||||
patrick
 | 
			
		||||
K 8
 | 
			
		||||
svn:date
 | 
			
		||||
V 27
 | 
			
		||||
2011-08-12T17:07:55.894416Z
 | 
			
		||||
K 7
 | 
			
		||||
svn:log
 | 
			
		||||
V 14
 | 
			
		||||
renamed a file
 | 
			
		||||
END
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
K 10
 | 
			
		||||
svn:author
 | 
			
		||||
V 7
 | 
			
		||||
patrick
 | 
			
		||||
K 8
 | 
			
		||||
svn:date
 | 
			
		||||
V 27
 | 
			
		||||
2011-08-12T17:08:12.646051Z
 | 
			
		||||
K 7
 | 
			
		||||
svn:log
 | 
			
		||||
V 14
 | 
			
		||||
deleted a file
 | 
			
		||||
END
 | 
			
		||||
@@ -0,0 +1,11 @@
 | 
			
		||||
PLAIN
 | 
			
		||||
END
 | 
			
		||||
ENDREP
 | 
			
		||||
id: 0.0.r0/17
 | 
			
		||||
type: dir
 | 
			
		||||
count: 0
 | 
			
		||||
text: 0 0 4 4 2d2977d1c96f487abe4a1e202dd03b4e
 | 
			
		||||
cpath: /
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
17 107
 | 
			
		||||
@@ -0,0 +1,28 @@
 | 
			
		||||
DELTA
 | 
			
		||||
SVNENDREP
 | 
			
		||||
id: 0-1.0.r1/17
 | 
			
		||||
type: file
 | 
			
		||||
count: 0
 | 
			
		||||
text: 1 0 4 0 d41d8cd98f00b204e9800998ecf8427e da39a3ee5e6b4b0d3255bfef95601890afd80709 0-0/_2
 | 
			
		||||
cpath: /new-file
 | 
			
		||||
copyroot: 0 /
 | 
			
		||||
 | 
			
		||||
PLAIN
 | 
			
		||||
K 8
 | 
			
		||||
new-file
 | 
			
		||||
V 16
 | 
			
		||||
file 0-1.0.r1/17
 | 
			
		||||
END
 | 
			
		||||
ENDREP
 | 
			
		||||
id: 0.0.r1/232
 | 
			
		||||
type: dir
 | 
			
		||||
pred: 0.0.r0/17
 | 
			
		||||
count: 1
 | 
			
		||||
text: 1 180 39 39 4a0c4e617d69f8a0bf11161d1e1b09a6
 | 
			
		||||
cpath: /
 | 
			
		||||
copyroot: 0 /
 | 
			
		||||
 | 
			
		||||
_0.0.t0-0 add-file true false /new-file
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
232 357
 | 
			
		||||
@@ -0,0 +1,29 @@
 | 
			
		||||
id: 0-1.0-2.r2/0
 | 
			
		||||
type: file
 | 
			
		||||
pred: 0-1.0.r1/17
 | 
			
		||||
count: 1
 | 
			
		||||
text: 1 0 4 0 d41d8cd98f00b204e9800998ecf8427e da39a3ee5e6b4b0d3255bfef95601890afd80709 0-0/_2
 | 
			
		||||
cpath: /alternate-name
 | 
			
		||||
copyfrom: 1 /new-file
 | 
			
		||||
 | 
			
		||||
PLAIN
 | 
			
		||||
K 14
 | 
			
		||||
alternate-name
 | 
			
		||||
V 17
 | 
			
		||||
file 0-1.0-2.r2/0
 | 
			
		||||
END
 | 
			
		||||
ENDREP
 | 
			
		||||
id: 0.0.r2/256
 | 
			
		||||
type: dir
 | 
			
		||||
pred: 0.0.r1/232
 | 
			
		||||
count: 2
 | 
			
		||||
text: 2 196 47 47 518568bcb7e6fe3165235a9f9993f5e1
 | 
			
		||||
cpath: /
 | 
			
		||||
copyroot: 0 /
 | 
			
		||||
 | 
			
		||||
0-1.0.r1/17 delete-file false false /new-file
 | 
			
		||||
 | 
			
		||||
0-1._0.t1-1 add-file false false /alternate-name
 | 
			
		||||
1 /new-file
 | 
			
		||||
 | 
			
		||||
256 382
 | 
			
		||||
@@ -0,0 +1,15 @@
 | 
			
		||||
PLAIN
 | 
			
		||||
END
 | 
			
		||||
ENDREP
 | 
			
		||||
id: 0.0.r3/17
 | 
			
		||||
type: dir
 | 
			
		||||
pred: 0.0.r2/256
 | 
			
		||||
count: 3
 | 
			
		||||
text: 3 0 4 4 2d2977d1c96f487abe4a1e202dd03b4e
 | 
			
		||||
cpath: /
 | 
			
		||||
copyroot: 0 /
 | 
			
		||||
 | 
			
		||||
0-1.0-2.r2/0 delete-file false false /alternate-name
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
17 138
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
3
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
5d5eaf70-0fe3-4d07-ada0-45c336a5bfad
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
5
 | 
			
		||||
		Reference in New Issue
	
	Block a user