private void updateGitStatus() throws IOException, ParseException {
		long now = System.currentTimeMillis();
		Path gtl;
		synchronized(gitToplevelLock) {
			gtl = gitToplevel;
		}
		GitStatus newStatus;
		if(gtl == null) {
			// Java 1.8: Inline this
			List<UncommittedChange> emptyList = Collections.emptyList();
			newStatus = new GitStatus(now, State.DISABLED, emptyList);
		} else {
			// Get a list of modules including "" for main module
			List<String> modules;
			{
				if(DEBUG) log("Finding modules");
				ProcessBuilder pb =
					new ProcessBuilder("git", "submodule", "--quiet", "foreach", "--recursive", "echo \"$path\"")
					.directory(gtl.toFile())
				;
				Process p = pb.start();
				ProcessResult result = ProcessResult.getProcessResult(p);
				if(result.getExitVal() != 0) throw new IOException("Unable to find submodules: " + result.getStderr());
				List<String> submodules = StringUtility.splitLines(result.getStdout());
				if(DEBUG) {
					for(String submodule : submodules) log("Got submodule: " + submodule);
				}
				// Add the empty module list
				modules = new ArrayList<>(submodules.size() + 1);
				modules.addAll(submodules);
				modules.add("");
			}
			// Get the status of each module
			State state = State.SYNCHRONIZED;
			List<UncommittedChange> uncommittedChanges = new ArrayList<>();
			for(String module : modules) {
				if(DEBUG) log("Getting status of module \"" + module + "\"");
				File workingDir;
				if(module.isEmpty()) {
					// Main module
					workingDir = gtl.toFile();
				} else {
					// Submodule
					workingDir = new File(gtl.toFile(), module);
				}
				ProcessBuilder pb =
					new ProcessBuilder("git", "status", "--porcelain", "-z")
					.directory(workingDir)
				;
				Process p = pb.start();
				ProcessResult result = ProcessResult.getProcessResult(p);
				if(result.getExitVal() != 0) throw new IOException("Unable to get status: " + result.getStderr());
				// Split on NUL (ASCII 0)
				List<String> split = new ArrayList<>(StringUtility.splitString(result.getStdout(), (char)0));
				if(!split.isEmpty()) {
					// Remove last empty part of split
					String last = split.remove(split.size()-1);
					if(!last.isEmpty()) throw new ParseException("Last element of split is not empty: " + last, 0);
				}
				//if((split.size() % 2) != 0) throw new ParseException("Unexpected split size: " + split.size(), 0);
				int i = 0;
				while(i < split.size()) {
					char x;
					char y;
					String from;
					String to;
					{
						String first = split.get(i++);
						if(first.length() < 3) throw new ParseException("split1 length too short: " + first.length(), 0);
						x = first.charAt(0);
						y = first.charAt(1);
						if(first.charAt(2) != ' ') throw new ParseException("Third character of split1 is not a space: " + first.charAt(2), 0);
						if(x == 'R') {
							// Is rename, will have both to and from
							to = first.substring(3);
							from = split.get(i++);
						} else {
							// Will have from only from
							to = null;
							from = first.substring(3);
						}
					}
					if(DEBUG) {
						log("x = \"" + x + "\"");
						log("y = \"" + y + "\"");
						log("from = \"" + from + "\"");
						log("to = \"" + to + "\"");
					}
					UncommittedChange uncommittedChange = new UncommittedChange(x, y, module, from, to);
					uncommittedChanges.add(uncommittedChange);
					// Keep the highest state
					State meaningState = uncommittedChange.getMeaning().getState();
					if(meaningState.compareTo(state) > 0) state = meaningState;
				}
			}
			if(DEBUG) log("state = " + state);
			// TODO: auto-commit any modified tasklogs
			// TODO: git pull all modules always to stay in sync
			// TODO: git push all modules if state is synchronized
			newStatus = new GitStatus(now, state, Collections.unmodifiableList(uncommittedChanges));
		}
		synchronized(statusLock) {
			status = newStatus;
		}
	}

	/**
	 * Gets the current Git status, will not block.
	 */
	public GitStatus getGitStatus() {
		long now = System.currentTimeMillis();
		synchronized(statusLock) {
			// Timeout when status not updated recently enough
			long statusTime = status.getStatusTime();
			long millisSince = now - statusTime;
			if(
				millisSince >= TIMEOUT_MILLIS
				|| millisSince <= -TIMEOUT_MILLIS // Can happen when system time reset
			) {
				// Java 1.8: Inline this
				List<UncommittedChange> emptyList = Collections.emptyList();
				return new GitStatus(statusTime, State.TIMEOUT, emptyList);
			}
			// Return most recently determined status
			return status;
		}
	}

	public static AutoGitContextListener getInstance(ServletContext sc) {
		return (AutoGitContextListener)sc.getAttribute(APPLICATION_SCOPE_KEY);
	}

	private static final String GIT_STATUS_REQUEST_CACHE_KEY = AutoGitContextListener.class.getName() + ".getGitStatus.cache";

	public static GitStatus getGitStatus(
		ServletContext servletContext,
		ServletRequest request
	) {
		// Look for cached value
		GitStatus gitStatus = (GitStatus)request.getAttribute(GIT_STATUS_REQUEST_CACHE_KEY);
		if(gitStatus == null) {
			AutoGitContextListener gitContext = AutoGitContextListener.getInstance(servletContext);
			if(gitContext == null) {
				// Java 1.8: Inline this
				List<UncommittedChange> emptyList = Collections.emptyList();
				gitStatus = new GitStatus(System.currentTimeMillis(), State.DISABLED, emptyList);
			} else {
				gitStatus = gitContext.getGitStatus();
			}
			request.setAttribute(GIT_STATUS_REQUEST_CACHE_KEY, gitStatus);
		}
		return gitStatus;
	}
}
private void updateGitStatus() throws IOException, ParseException {
		long now = System.currentTimeMillis();
		Path gtl;
		synchronized(gitToplevelLock) {
			gtl = gitToplevel;
		}
		GitStatus newStatus;
		if(gtl == null) {
			
			List<UncommittedChange> emptyList = Collections.emptyList();
			newStatus = new GitStatus(now, State.DISABLED, emptyList);
		} else {
			
			List<String> modules;
			{
				if(DEBUG) log("Finding modules");
				ProcessBuilder pb =
					new ProcessBuilder("git", "submodule", "--quiet", "foreach", "--recursive", "echo \"$path\"")
					.directory(gtl.toFile())
				;
				Process p = pb.start();
				ProcessResult result = ProcessResult.getProcessResult(p);
				if(result.getExitVal() != 0) throw new IOException("Unable to find submodules: " + result.getStderr());
				List<String> submodules = StringUtility.splitLines(result.getStdout());
				if(DEBUG) {
					for(String submodule : submodules) log("Got submodule: " + submodule);
				}
				
				modules = new ArrayList<>(submodules.size() + 1);
				modules.addAll(submodules);
				modules.add("");
			}
			
			State state = State.SYNCHRONIZED;
			List<UncommittedChange> uncommittedChanges = new ArrayList<>();
			for(String module : modules) {
				if(DEBUG) log("Getting status of module \"" + module + "\"");
				File workingDir;
				if(module.isEmpty()) {
					
					workingDir = gtl.toFile();
				} else {
					
					workingDir = new File(gtl.toFile(), module);
				}
				ProcessBuilder pb =
					new ProcessBuilder("git", "status", "--porcelain", "-z")
					.directory(workingDir)
				;
				Process p = pb.start();
				ProcessResult result = ProcessResult.getProcessResult(p);
				if(result.getExitVal() != 0) throw new IOException("Unable to get status: " + result.getStderr());
				
				List<String> split = new ArrayList<>(StringUtility.splitString(result.getStdout(), (char)0));
				if(!split.isEmpty()) {
					
					String last = split.remove(split.size()-1);
					if(!last.isEmpty()) throw new ParseException("Last element of split is not empty: " + last, 0);
				}
				
				int i = 0;
				while(i < split.size()) {
					char x;
					char y;
					String from;
					String to;
					{
						String first = split.get(i++);
						if(first.length() < 3) throw new ParseException("split1 length too short: " + first.length(), 0);
						x = first.charAt(0);
						y = first.charAt(1);
						if(first.charAt(2) != ' ') throw new ParseException("Third character of split1 is not a space: " + first.charAt(2), 0);
						if(x == 'R') {
							
							to = first.substring(3);
							from = split.get(i++);
						} else {
							
							to = null;
							from = first.substring(3);
						}
					}
					if(DEBUG) {
						log("x = \"" + x + "\"");
						log("y = \"" + y + "\"");
						log("from = \"" + from + "\"");
						log("to = \"" + to + "\"");
					}
					UncommittedChange uncommittedChange = new UncommittedChange(x, y, module, from, to);
					uncommittedChanges.add(uncommittedChange);
					
					State meaningState = uncommittedChange.getMeaning().getState();
					if(meaningState.compareTo(state) > 0) state = meaningState;
				}
			}
			if(DEBUG) log("state = " + state);
			
			
			
			newStatus = new GitStatus(now, state, Collections.unmodifiableList(uncommittedChanges));
		}
		synchronized(statusLock) {
			status = newStatus;
		}
	}

	
	public GitStatus getGitStatus() {
		long now = System.currentTimeMillis();
		synchronized(statusLock) {
			
			long statusTime = status.getStatusTime();
			long millisSince = now - statusTime;
			if(
				millisSince >= TIMEOUT_MILLIS
				|| millisSince <= -TIMEOUT_MILLIS 
			) {
				
				List<UncommittedChange> emptyList = Collections.emptyList();
				return new GitStatus(statusTime, State.TIMEOUT, emptyList);
			}
			
			return status;
		}
	}

	public static AutoGitContextListener getInstance(ServletContext sc) {
		return (AutoGitContextListener)sc.getAttribute(APPLICATION_SCOPE_KEY);
	}

	private static final String GIT_STATUS_REQUEST_CACHE_KEY = AutoGitContextListener.class.getName() + ".getGitStatus.cache";

	public static GitStatus getGitStatus(
		ServletContext servletContext,
		ServletRequest request
	) {
		
		GitStatus gitStatus = (GitStatus)request.getAttribute(GIT_STATUS_REQUEST_CACHE_KEY);
		if(gitStatus == null) {
			AutoGitContextListener gitContext = AutoGitContextListener.getInstance(servletContext);
			if(gitContext == null) {
				
				List<UncommittedChange> emptyList = Collections.emptyList();
				gitStatus = new GitStatus(System.currentTimeMillis(), State.DISABLED, emptyList);
			} else {
				gitStatus = gitContext.getGitStatus();
			}
			request.setAttribute(GIT_STATUS_REQUEST_CACHE_KEY, gitStatus);
		}
		return gitStatus;
	}
}
private void updateGitStatus ( ) throws IOException , ParseException { long now = System . currentTimeMillis ( ) ; Path gtl ; synchronized ( gitToplevelLock ) { gtl = gitToplevel ; } GitStatus newStatus ; if ( gtl == null ) { // Java 1.8: Inline this List < UncommittedChange > emptyList = Collections . emptyList ( ) ; newStatus = new GitStatus ( now , State . DISABLED , emptyList ) ; } else { // Get a list of modules including "" for main module List < String > modules ; { if ( DEBUG ) log ( "Finding modules" ) ; ProcessBuilder pb = new ProcessBuilder ( "git" , "submodule" , "--quiet" , "foreach" , "--recursive" , "echo \"$path\"" ) . directory ( gtl . toFile ( ) ) ; Process p = pb . start ( ) ; ProcessResult result = ProcessResult . getProcessResult ( p ) ; if ( result . getExitVal ( ) != 0 ) throw new IOException ( "Unable to find submodules: " + result . getStderr ( ) ) ; List < String > submodules = StringUtility . splitLines ( result . getStdout ( ) ) ; if ( DEBUG ) { for ( String submodule : submodules ) log ( "Got submodule: " + submodule ) ; } // Add the empty module list modules = new ArrayList <> ( submodules . size ( ) + 1 ) ; modules . addAll ( submodules ) ; modules . add ( "" ) ; } // Get the status of each module State state = State . SYNCHRONIZED ; List < UncommittedChange > uncommittedChanges = new ArrayList <> ( ) ; for ( String module : modules ) { if ( DEBUG ) log ( "Getting status of module \"" + module + "\"" ) ; File workingDir ; if ( module . isEmpty ( ) ) { // Main module workingDir = gtl . toFile ( ) ; } else { // Submodule workingDir = new File ( gtl . toFile ( ) , module ) ; } ProcessBuilder pb = new ProcessBuilder ( "git" , "status" , "--porcelain" , "-z" ) . directory ( workingDir ) ; Process p = pb . start ( ) ; ProcessResult result = ProcessResult . getProcessResult ( p ) ; if ( result . getExitVal ( ) != 0 ) throw new IOException ( "Unable to get status: " + result . getStderr ( ) ) ; // Split on NUL (ASCII 0) List < String > split = new ArrayList <> ( StringUtility . splitString ( result . getStdout ( ) , ( char ) 0 ) ) ; if ( ! split . isEmpty ( ) ) { // Remove last empty part of split String last = split . remove ( split . size ( ) - 1 ) ; if ( ! last . isEmpty ( ) ) throw new ParseException ( "Last element of split is not empty: " + last , 0 ) ; } //if((split.size() % 2) != 0) throw new ParseException("Unexpected split size: " + split.size(), 0); int i = 0 ; while ( i < split . size ( ) ) { char x ; char y ; String from ; String to ; { String first = split . get ( i ++ ) ; if ( first . length ( ) < 3 ) throw new ParseException ( "split1 length too short: " + first . length ( ) , 0 ) ; x = first . charAt ( 0 ) ; y = first . charAt ( 1 ) ; if ( first . charAt ( 2 ) != ' ' ) throw new ParseException ( "Third character of split1 is not a space: " + first . charAt ( 2 ) , 0 ) ; if ( x == ' ' ) { // Is rename, will have both to and from to = first . substring ( 3 ) ; from = split . get ( i ++ ) ; } else { // Will have from only from to = null ; from = first . substring ( 3 ) ; } } if ( DEBUG ) { log ( "x = \"" + x + "\"" ) ; log ( "y = \"" + y + "\"" ) ; log ( "from = \"" + from + "\"" ) ; log ( "to = \"" + to + "\"" ) ; } UncommittedChange uncommittedChange = new UncommittedChange ( x , y , module , from , to ) ; uncommittedChanges . add ( uncommittedChange ) ; // Keep the highest state State meaningState = uncommittedChange . getMeaning ( ) . getState ( ) ; if ( meaningState . compareTo ( state ) > 0 ) state = meaningState ; } } if ( DEBUG ) log ( "state = " + state ) ; // TODO: auto-commit any modified tasklogs // TODO: git pull all modules always to stay in sync // TODO: git push all modules if state is synchronized newStatus = new GitStatus ( now , state , Collections . unmodifiableList ( uncommittedChanges ) ) ; } synchronized ( statusLock ) { status = newStatus ; } } /**
	 * Gets the current Git status, will not block.
	 */ public GitStatus getGitStatus ( ) { long now = System . currentTimeMillis ( ) ; synchronized ( statusLock ) { // Timeout when status not updated recently enough long statusTime = status . getStatusTime ( ) ; long millisSince = now - statusTime ; if ( millisSince >= TIMEOUT_MILLIS || millisSince <= - TIMEOUT_MILLIS // Can happen when system time reset ) { // Java 1.8: Inline this List < UncommittedChange > emptyList = Collections . emptyList ( ) ; return new GitStatus ( statusTime , State . TIMEOUT , emptyList ) ; } // Return most recently determined status return status ; } } public static AutoGitContextListener getInstance ( ServletContext sc ) { return ( AutoGitContextListener ) sc . getAttribute ( APPLICATION_SCOPE_KEY ) ; } private static final String GIT_STATUS_REQUEST_CACHE_KEY = AutoGitContextListener . class . getName ( ) + ".getGitStatus.cache" ; public static GitStatus getGitStatus ( ServletContext servletContext , ServletRequest request ) { // Look for cached value GitStatus gitStatus = ( GitStatus ) request . getAttribute ( GIT_STATUS_REQUEST_CACHE_KEY ) ; if ( gitStatus == null ) { AutoGitContextListener gitContext = AutoGitContextListener . getInstance ( servletContext ) ; if ( gitContext == null ) { // Java 1.8: Inline this List < UncommittedChange > emptyList = Collections . emptyList ( ) ; gitStatus = new GitStatus ( System . currentTimeMillis ( ) , State . DISABLED , emptyList ) ; } else { gitStatus = gitContext . getGitStatus ( ) ; } request . setAttribute ( GIT_STATUS_REQUEST_CACHE_KEY , gitStatus ) ; } return gitStatus ; } }
Updates the current Git status, will block on I/O.
Automatically does git pull and git push when there are no uncommitted changes.
<p>
Called on startup.
Also called when file change detected and the Git status should be updated.
</p>
TODO: Add timeout within this method?
Updates the current Git status will block on I / O . Automatically does git pull and git push when there are no uncommitted changes . <p > Called on startup . Also called when file change detected and the Git status should be updated . < / p > TODO : Add timeout within this method?