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
91       * <code>relativeRepositoryPath</code>
92       * <pre>
93       * URI.create( repositoryRoot ).relativize( fileSet.getBasedir().toURI() )
94       * </pre>
95       * 
96       * @param logger the logger
97       * @param workingDirectory the working directory
98       * @param relativeRepositoryPath the working directory relative to the repository root
99       * @since 1.9
100      * @see GitStatusCommand#createRevparseShowToplevelCommand(org.apache.maven.scm.ScmFileSet)
101      */
102     public GitStatusConsumer( ScmLogger logger, File workingDirectory, URI relativeRepositoryPath )
103     {
104         this( logger, workingDirectory );
105         this.relativeRepositoryPath = relativeRepositoryPath;
106     }
107 
108     // ----------------------------------------------------------------------
109     // StreamConsumer Implementation
110     // ----------------------------------------------------------------------
111 
112     /**
113      * {@inheritDoc}
114      */
115     public void consumeLine( String line )
116     {
117         if ( logger.isDebugEnabled() )
118         {
119             logger.debug( line );
120         }
121         if ( StringUtils.isEmpty( line ) )
122         {
123             return;
124         }
125 
126         ScmFileStatus status = null;
127 
128         List<String> files = new ArrayList<String>();
129         
130         Matcher matcher;
131         if ( ( matcher = ADDED_PATTERN.matcher( line ) ).find() )
132         {
133             status = ScmFileStatus.ADDED;
134             files.add( resolvePath( matcher.group( 1 ), relativeRepositoryPath ) );
135         }
136         else if ( ( matcher = MODIFIED_PATTERN.matcher( line ) ).find() )
137         {
138             status = ScmFileStatus.MODIFIED;
139             files.add( resolvePath( matcher.group( 1 ), relativeRepositoryPath ) );
140         }
141         else if ( ( matcher = DELETED_PATTERN.matcher( line ) ).find() )
142         {
143             status = ScmFileStatus.DELETED;
144             files.add( resolvePath( matcher.group( 1 ), relativeRepositoryPath ) );
145         }
146         else if ( ( matcher = RENAMED_PATTERN.matcher( line ) ).find() )
147         {
148             status = ScmFileStatus.RENAMED;
149             files.add( resolvePath( matcher.group( 1 ), relativeRepositoryPath ) );
150             files.add( resolvePath( matcher.group( 2 ), relativeRepositoryPath ) );
151             logger.debug( "RENAMED status for line '" + line + "' files added '" + matcher.group( 1 ) + "' '"
152                               + matcher.group( 2 ) );
153         }
154         else
155         {
156             logger.warn( "Ignoring unrecognized line: " + line );
157             return;
158         }
159 
160         // If the file isn't a file; don't add it.
161         if ( !files.isEmpty() && status != null )
162         {
163             if ( workingDirectory != null )
164             {
165                 if ( status == ScmFileStatus.RENAMED )
166                 {
167                     String oldFilePath = files.get( 0 );
168                     String newFilePath = files.get( 1 );
169                     if ( isFile( oldFilePath ) )
170                     {
171                         logger.debug( "file '" + oldFilePath + "' is a file" );
172                         return;
173                     }
174                     else
175                     {
176                         logger.debug( "file '" + oldFilePath + "' not a file" );
177                     }
178                     if ( !isFile( newFilePath ) )
179                     {
180                         logger.debug( "file '" + newFilePath + "' not a file" );
181                         return;
182                     }
183                     else
184                     {
185                         logger.debug( "file '" + newFilePath + "' is a file" );
186                     }
187                 }
188                 else if ( status == ScmFileStatus.DELETED )
189                 {
190                     if ( isFile( files.get( 0 ) ) )
191                     {
192                         return;
193                     }
194                 }
195                 else
196                 {
197                     if ( !isFile( files.get( 0 ) ) )
198                     {
199                         return;
200                     }
201                 }
202             }
203 
204             for ( String file : files )
205             {
206                 changedFiles.add( new ScmFile( file, status ) );
207             }
208         }
209     }
210 
211     private boolean isFile( String file )
212     {
213         File targetFile;
214         if ( relativeRepositoryPath == null )
215         {
216             targetFile = new File( workingDirectory, file );
217         }
218         else
219         {
220             targetFile = new File( relativeRepositoryPath.getPath(), file );
221         }
222         return targetFile.isFile();
223     }
224 
225     protected static String resolvePath( String fileEntry, URI path )
226     {
227         if ( path != null )
228         {
229             return resolveURI( fileEntry, path ).getPath();
230         }
231         else
232         {
233             return fileEntry;
234         }
235     }
236 
237     /**
238      * 
239      * @param fileEntry the fileEntry, must not be {@code null}
240      * @param path the path, must not be {@code null}
241      * @return
242      */
243     public static URI resolveURI( String fileEntry, URI path )
244     {
245         // When using URI.create, spaces need to be escaped but not the slashes, so we can't use
246         // URLEncoder.encode( String, String )
247         // new File( String ).toURI() results in an absolute URI while path is relative, so that can't be used either.
248         String str = fileEntry.replace( " ", "%20" );
249         return path.relativize( URI.create( str ) );
250     }
251 
252 
253     public List<ScmFile> getChangedFiles()
254     {
255         return changedFiles;
256     }
257 }