View Javadoc
1   package org.apache.maven.scm.provider.git.gitexe.command.status;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   * http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.net.URI;
24  import java.util.ArrayList;
25  import java.util.List;
26  import java.util.regex.Matcher;
27  import java.util.regex.Pattern;
28  
29  import org.apache.commons.lang.StringUtils;
30  import org.apache.maven.scm.ScmFile;
31  import org.apache.maven.scm.ScmFileStatus;
32  import org.apache.maven.scm.log.ScmLogger;
33  import org.codehaus.plexus.util.cli.StreamConsumer;
34  
35  /**
36   * @author <a href="mailto:struberg@yahoo.de">Mark Struberg</a>
37   */
38  public class GitStatusConsumer
39      implements StreamConsumer
40  {
41  
42      /**
43       * The pattern used to match added file lines
44       */
45      private static final Pattern ADDED_PATTERN = Pattern.compile( "^A[ M]* (.*)$" );
46  
47      /**
48       * The pattern used to match modified file lines
49       */
50      private static final Pattern MODIFIED_PATTERN = Pattern.compile( "^ *M[ M]* (.*)$" );
51  
52      /**
53       * The pattern used to match deleted file lines
54       */
55      private static final Pattern DELETED_PATTERN = Pattern.compile( "^ *D * (.*)$" );
56  
57      /**
58       * The pattern used to match renamed file lines
59       */
60      private static final Pattern RENAMED_PATTERN = Pattern.compile( "^R  (.*) -> (.*)$" );
61  
62      private ScmLogger logger;
63  
64      private File workingDirectory;
65  
66      /**
67       * Entries are relative to working directory, not to the repositoryroot
68       */
69      private List<ScmFile> changedFiles = new ArrayList<ScmFile>();
70  
71      private URI relativeRepositoryPath;
72      
73      // ----------------------------------------------------------------------
74      //
75      // ----------------------------------------------------------------------
76  
77      /**
78       * Consumer when workingDirectory and repositoryRootDirectory are the same
79       * 
80       * @param logger the logger
81       * @param workingDirectory the working directory
82       */
83      public GitStatusConsumer( ScmLogger logger, File workingDirectory )
84      {
85          this.logger = logger;
86          this.workingDirectory = workingDirectory;
87      }
88  
89      /**
90       * Assuming that you have to discover the repositoryRoot, this is how you can get the <code>relativeRepositoryPath</code>
91       * <pre>
92       * URI.create( repositoryRoot ).relativize( fileSet.getBasedir().toURI() )
93       * </pre>
94       * 
95       * @param logger the logger
96       * @param workingDirectory the working directory
97       * @param relativeRepositoryPath the working directory relative to the repository root
98       * @since 1.9
99       * @see GitStatusCommand#createRevparseShowToplevelCommand(org.apache.maven.scm.ScmFileSet)
100      */
101     public GitStatusConsumer( ScmLogger logger, File workingDirectory, URI relativeRepositoryPath )
102     {
103         this( logger, workingDirectory );
104         this.relativeRepositoryPath = relativeRepositoryPath;
105     }
106 
107     // ----------------------------------------------------------------------
108     // StreamConsumer Implementation
109     // ----------------------------------------------------------------------
110 
111     /**
112      * {@inheritDoc}
113      */
114     public void consumeLine( String line )
115     {
116         if ( logger.isDebugEnabled() )
117         {
118             logger.debug( line );
119         }
120         if ( StringUtils.isEmpty( line ) )
121         {
122             return;
123         }
124 
125         ScmFileStatus status = null;
126 
127         List<String> files = new ArrayList<String>();
128         
129         Matcher matcher;
130         if ( ( matcher = ADDED_PATTERN.matcher( line ) ).find() )
131         {
132             status = ScmFileStatus.ADDED;
133             files.add(resolvePath(matcher.group(1), relativeRepositoryPath));
134         }
135         else if ( ( matcher = MODIFIED_PATTERN.matcher( line ) ).find() )
136         {
137             status = ScmFileStatus.MODIFIED;
138             files.add(resolvePath(matcher.group(1), relativeRepositoryPath));
139         }
140         else if ( ( matcher = DELETED_PATTERN.matcher( line ) ) .find() )
141         {
142             status = ScmFileStatus.DELETED;
143             files.add(resolvePath(matcher.group(1), relativeRepositoryPath));
144         }
145         else if ( ( matcher = RENAMED_PATTERN.matcher( line ) ).find() )
146         {
147             status = ScmFileStatus.RENAMED;
148             files.add( resolvePath( matcher.group( 1 ), relativeRepositoryPath ) );
149             files.add( resolvePath( matcher.group( 2 ), relativeRepositoryPath ) );
150             logger.debug( "RENAMED status for line '" + line + "' files added '" + matcher.group( 1 ) + "' '"
151                               + matcher.group( 2 ) );
152         }
153         else
154         {
155         	logger.warn( "Ignoring unrecognized line: " +  line );
156         	return;
157         }
158 
159         // If the file isn't a file; don't add it.
160         if ( !files.isEmpty() && status != null )
161         {
162             if ( workingDirectory != null )
163             {
164                 if ( status == ScmFileStatus.RENAMED )
165                 {
166                     String oldFilePath = files.get( 0 );
167                     String newFilePath = files.get( 1 );
168                     if ( isFile( oldFilePath ) )
169                     {
170                         logger.debug(
171                             "file '" + oldFilePath + "' is a file" );
172                         return;
173                     }
174                     else
175                     {
176                         logger.debug(
177                             "file '" + oldFilePath + "' not a file" );
178                     }
179                     if ( !isFile( newFilePath ) )
180                     {
181                         logger.debug(
182                             "file '" + newFilePath + "' not a file" );
183                         return;
184                     }
185                     else
186                     {
187                         logger.debug(
188                             "file '" + newFilePath + "' is a file" );
189                     }
190                 }
191                 else if ( status == ScmFileStatus.DELETED )
192                 {
193                     if ( isFile( files.get( 0 ) ) )
194                     {
195                         return;
196                     }
197                 }
198                 else
199                 {
200                     if ( !isFile( files.get( 0 ) ) )
201                     {
202                         return;
203                     }
204                 }
205             }
206 
207             for ( String file : files )
208             {
209                 changedFiles.add( new ScmFile( file, status ) );
210             }
211         }
212     }
213 
214     private boolean isFile( String file )
215     {
216         File targetFile;
217         if ( relativeRepositoryPath == null )
218         {
219             targetFile = new File( workingDirectory, file );
220         }
221         else
222         {
223             targetFile = new File( relativeRepositoryPath.getPath(), file );
224         }
225         return targetFile.isFile();
226     }
227 
228     protected static String resolvePath( String fileEntry, URI path )
229     {
230         if ( path != null )
231         {
232             return resolveURI( fileEntry, path ).getPath();
233         }
234         else
235         {
236             return fileEntry;
237         }
238     }
239 
240     /**
241      * 
242      * @param fileEntry the fileEntry, must not be {@code null}
243      * @param path the path, must not be {@code null}
244      * @return
245      */
246     public static URI resolveURI( String fileEntry, URI path )
247     {
248         // When using URI.create, spaces need to be escaped but not the slashes, so we can't use URLEncoder.encode( String, String )
249         // new File( String ).toURI() results in an absolute URI while path is relative, so that can't be used either.
250         String str = fileEntry.replace( " ", "%20" );
251         return path.relativize( URI.create( str ) );
252     }
253 
254 
255     public List<ScmFile> getChangedFiles()
256     {
257         return changedFiles;
258     }
259 }