View Javadoc
1   /*
2    * Copyright (C) 2022, Matthias Fromme <mfromme@dspace.de>
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  package org.eclipse.jgit.lfs.internal;
11  
12  import java.io.File;
13  import java.io.IOException;
14  
15  import org.eclipse.jgit.annotations.Nullable;
16  import org.eclipse.jgit.dircache.DirCacheEntry;
17  import org.eclipse.jgit.errors.ConfigInvalidException;
18  import org.eclipse.jgit.lfs.errors.LfsConfigInvalidException;
19  import org.eclipse.jgit.lfs.lib.Constants;
20  import org.eclipse.jgit.lib.BlobBasedConfig;
21  import org.eclipse.jgit.lib.Config;
22  import org.eclipse.jgit.lib.ObjectId;
23  import org.eclipse.jgit.lib.Repository;
24  import org.eclipse.jgit.revwalk.RevCommit;
25  import org.eclipse.jgit.revwalk.RevTree;
26  import org.eclipse.jgit.revwalk.RevWalk;
27  import org.eclipse.jgit.storage.file.FileBasedConfig;
28  import org.eclipse.jgit.treewalk.TreeWalk;
29  
30  import static org.eclipse.jgit.lib.Constants.HEAD;
31  
32  /**
33   * Encapsulate access to the .lfsconfig.
34   *
35   * According to the document
36   * https://github.com/git-lfs/git-lfs/blob/main/docs/man/git-lfs-config.5.ronn
37   * the order to find the .lfsconfig file is:
38   *
39   * <pre>
40   *   1. in the root of the working tree
41   *   2. in the index
42   *   3. in the HEAD, for bare repositories this is the only place
43   *      that is searched
44   * </pre>
45   *
46   * Values from the .lfsconfig are used only if not specified in another git
47   * config file to allow local override without modifiction of a committed file.
48   */
49  public class LfsConfig {
50  	private Repository db;
51  	private Config delegate;
52  
53  	/**
54  	 * Create a new instance of the LfsConfig.
55  	 *
56  	 * @param db
57  	 *            the associated repo
58  	 * @throws IOException
59  	 */
60  	public LfsConfig(Repository db) throws IOException {
61  		this.db = db;
62  		delegate = this.load();
63  	}
64  
65  	/**
66  	 * Read the .lfsconfig file from the repository
67  	 *
68  	 * @return The loaded lfs config or null if it does not exist
69  	 *
70  	 * @throws IOException
71  	 */
72  	private Config load() throws IOException {
73  		Config result = null;
74  
75  		if (!db.isBare()) {
76  			result = loadFromWorkingTree();
77  			if (result == null) {
78  				result = loadFromIndex();
79  			}
80  		}
81  
82  		if (result == null) {
83  			result = loadFromHead();
84  		}
85  
86  		if (result == null) {
87  			result = emptyConfig();
88  		}
89  
90  		return result;
91  	}
92  
93  	/**
94  	 * Try to read the lfs config from a file called .lfsconfig at the top level
95  	 * of the working tree.
96  	 *
97  	 * @return the config, or <code>null</code>
98  	 * @throws IOException
99  	 */
100 	@Nullable
101 	private Config loadFromWorkingTree()
102 			throws IOException {
103 		File lfsConfig = db.getFS().resolve(db.getWorkTree(),
104 				Constants.DOT_LFS_CONFIG);
105 		if (lfsConfig.exists() && lfsConfig.isFile()) {
106 			FileBasedConfig config = new FileBasedConfig(lfsConfig, db.getFS());
107 			try {
108 				config.load();
109 				return config;
110 			} catch (ConfigInvalidException e) {
111 				throw new LfsConfigInvalidException(
112 						LfsText.get().dotLfsConfigReadFailed, e);
113 			}
114 		}
115 		return null;
116 	}
117 
118 	/**
119 	 * Try to read the lfs config from an entry called .lfsconfig contained in
120 	 * the index.
121 	 *
122 	 * @return the config, or <code>null</code> if the entry does not exist
123 	 * @throws IOException
124 	 */
125 	@Nullable
126 	private Config loadFromIndex()
127 			throws IOException {
128 		try {
129 			DirCacheEntry entry = db.readDirCache()
130 					.getEntry(Constants.DOT_LFS_CONFIG);
131 			if (entry != null) {
132 				return new BlobBasedConfig(null, db, entry.getObjectId());
133 			}
134 		} catch (ConfigInvalidException e) {
135 			throw new LfsConfigInvalidException(
136 					LfsText.get().dotLfsConfigReadFailed, e);
137 		}
138 		return null;
139 	}
140 
141 	/**
142 	 * Try to read the lfs config from an entry called .lfsconfig contained in
143 	 * the head revision.
144 	 *
145 	 * @return the config, or <code>null</code> if the file does not exist
146 	 * @throws IOException
147 	 */
148 	@Nullable
149 	private Config loadFromHead() throws IOException {
150 		try (RevWalk revWalk = new RevWalk(db)) {
151 			ObjectId headCommitId = db.resolve(HEAD);
152 			if (headCommitId == null) {
153 				return null;
154 			}
155 			RevCommit commit = revWalk.parseCommit(headCommitId);
156 			RevTree tree = commit.getTree();
157 			TreeWalk treewalk = TreeWalk.forPath(db, Constants.DOT_LFS_CONFIG,
158 					tree);
159 			if (treewalk != null) {
160 				return new BlobBasedConfig(null, db, treewalk.getObjectId(0));
161 			}
162 		} catch (ConfigInvalidException e) {
163 			throw new LfsConfigInvalidException(
164 					LfsText.get().dotLfsConfigReadFailed, e);
165 		}
166 		return null;
167 	}
168 
169 	/**
170 	 * Create an empty config as fallback to avoid null pointer checks.
171 	 *
172 	 * @return an empty config
173 	 */
174 	private Config emptyConfig() {
175 		return new Config();
176 	}
177 
178 	/**
179 	 * Get string value or null if not found.
180 	 *
181 	 * First tries to find the value in the git config files. If not found tries
182 	 * to find data in .lfsconfig.
183 	 *
184 	 * @param section
185 	 *            the section
186 	 * @param subsection
187 	 *            the subsection for the value
188 	 * @param name
189 	 *            the key name
190 	 * @return a String value from the config, <code>null</code> if not found
191 	 */
192 	public String getString(final String section, final String subsection,
193 			final String name) {
194 		String result = db.getConfig().getString(section, subsection, name);
195 		if (result == null) {
196 			result = delegate.getString(section, subsection, name);
197 		}
198 		return result;
199 	}
200 }