You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RefUpdateTest.java 36KB

Rewrite reference handling to be abstract and accurate This commit actually does three major changes to the way references are handled within JGit. Unfortunately they were easier to do as a single massive commit than to break them up into smaller units. Disambiguate symbolic references: --------------------------------- Reporting a symbolic reference such as HEAD as though it were any other normal reference like refs/heads/master causes subtle programming errors. We have been bitten by this error on several occasions, as have some downstream applications written by myself. Instead of reporting HEAD as a reference whose name differs from its "original name", report it as an actual SymbolicRef object that the application can test the type and examine the target of. With this change, Ref is now an abstract type with different subclasses for the different types. In the classical example of "HEAD" being a symbolic reference to branch "refs/heads/master", the Repository.getAllRefs() method will now return: Map<String, Ref> all = repository.getAllRefs(); SymbolicRef HEAD = (SymbolicRef) all.get("HEAD"); ObjectIdRef master = (ObjectIdRef) all.get("refs/heads/master"); assertSame(master, HEAD.getTarget()); assertSame(master.getObjectId(), HEAD.getObjectId()); assertEquals("HEAD", HEAD.getName()); assertEquals("refs/heads/master", master.getName()); A nice side-effect of this change is the storage type of the symbolic reference is no longer ambiguous with the storge type of the underlying reference it targets. In the above example, if master was only available in the packed-refs file, then the following is also true: assertSame(Ref.Storage.LOOSE, HEAD.getStorage()); assertSame(Ref.Storage.PACKED, master.getStorage()); (Prior to this change we returned the ambiguous storage of LOOSE_PACKED for HEAD, which was confusing since it wasn't actually true on disk). Another nice side-effect of this change is all intermediate symbolic references are preserved, and are therefore visible to the application when they walk the target chain. We can now correctly inspect chains of symbolic references. As a result of this change the Ref.getOrigName() method has been removed from the API. Applications should identify a symbolic reference by testing for isSymbolic() and not by using an arcane string comparsion between properties. Abstract the RefDatabase storage: --------------------------------- RefDatabase is now abstract, similar to ObjectDatabase, and a new concrete implementation called RefDirectory is used for the traditional on-disk storage layout. In the future we plan to support additional implementations, such as a pure in-memory RefDatabase for unit testing purposes. Optimize RefDirectory: ---------------------- The implementation of the in-memory reference cache, reading, and update routines has been completely rewritten. Much of the code was heavily borrowed or cribbed from the prior implementation, so copyright notices have been left intact as much as possible. The RefDirectory cache no longer confuses symbolic references with normal references. This permits the cache to resolve the value of a symbolic reference as late as possible, ensuring it is always current, without needing to maintain reverse pointers. The cache is now 2 sorted RefLists, rather than 3 HashMaps. Using sorted lists allows the implementation to reduce the in-memory footprint when storing many refs. Using specialized types for the elements allows the code to avoid additional map lookups for auxiliary stat information. To improve scan time during getRefs(), the lists are returned via a copy-on-write contract. Most callers of getRefs() do not modify the returned collections, so the copy-on-write semantics improves access on repositories with a large number of packed references. Iterator traversals of the returned Map<String,Ref> are performed using a simple merge-join of the two cache lists, ensuring we can perform the entire traversal in linear time as a function of the number of references: O(PackedRefs + LooseRefs). Scans of the loose reference space to update the cache run in O(LooseRefs log LooseRefs) time, as the directory contents are sorted before being merged against the in-memory cache. Since the majority of stable references are kept packed, there typically are only a handful of reference names to be sorted, so the sorting cost should not be very high. Locking is reduced during getRefs() by taking advantage of the copy-on-write semantics of the improved cache data structure. This permits concurrent readers to pull back references without blocking each other. If there is contention updating the cache during a scan, one or more updates are simply skipped and will get picked up again in a future scan. Writing to the $GIT_DIR/packed-refs during reference delete is now fully atomic. The file is locked, reparsed fresh, and written back out if a change is necessary. This avoids all race conditions with concurrent external updates of the packed-refs file. The RefLogWriter class has been fully folded into RefDirectory and is therefore deleted. Maintaining the reference's log is the responsiblity of the database implementation, and not all implementations will use java.io for access. Future work still remains to be done to abstract the ReflogReader class away from local disk IO. Change-Id: I26b9287c45a4b2d2be35ba2849daa316f5eec85d Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
Rewrite reference handling to be abstract and accurate This commit actually does three major changes to the way references are handled within JGit. Unfortunately they were easier to do as a single massive commit than to break them up into smaller units. Disambiguate symbolic references: --------------------------------- Reporting a symbolic reference such as HEAD as though it were any other normal reference like refs/heads/master causes subtle programming errors. We have been bitten by this error on several occasions, as have some downstream applications written by myself. Instead of reporting HEAD as a reference whose name differs from its "original name", report it as an actual SymbolicRef object that the application can test the type and examine the target of. With this change, Ref is now an abstract type with different subclasses for the different types. In the classical example of "HEAD" being a symbolic reference to branch "refs/heads/master", the Repository.getAllRefs() method will now return: Map<String, Ref> all = repository.getAllRefs(); SymbolicRef HEAD = (SymbolicRef) all.get("HEAD"); ObjectIdRef master = (ObjectIdRef) all.get("refs/heads/master"); assertSame(master, HEAD.getTarget()); assertSame(master.getObjectId(), HEAD.getObjectId()); assertEquals("HEAD", HEAD.getName()); assertEquals("refs/heads/master", master.getName()); A nice side-effect of this change is the storage type of the symbolic reference is no longer ambiguous with the storge type of the underlying reference it targets. In the above example, if master was only available in the packed-refs file, then the following is also true: assertSame(Ref.Storage.LOOSE, HEAD.getStorage()); assertSame(Ref.Storage.PACKED, master.getStorage()); (Prior to this change we returned the ambiguous storage of LOOSE_PACKED for HEAD, which was confusing since it wasn't actually true on disk). Another nice side-effect of this change is all intermediate symbolic references are preserved, and are therefore visible to the application when they walk the target chain. We can now correctly inspect chains of symbolic references. As a result of this change the Ref.getOrigName() method has been removed from the API. Applications should identify a symbolic reference by testing for isSymbolic() and not by using an arcane string comparsion between properties. Abstract the RefDatabase storage: --------------------------------- RefDatabase is now abstract, similar to ObjectDatabase, and a new concrete implementation called RefDirectory is used for the traditional on-disk storage layout. In the future we plan to support additional implementations, such as a pure in-memory RefDatabase for unit testing purposes. Optimize RefDirectory: ---------------------- The implementation of the in-memory reference cache, reading, and update routines has been completely rewritten. Much of the code was heavily borrowed or cribbed from the prior implementation, so copyright notices have been left intact as much as possible. The RefDirectory cache no longer confuses symbolic references with normal references. This permits the cache to resolve the value of a symbolic reference as late as possible, ensuring it is always current, without needing to maintain reverse pointers. The cache is now 2 sorted RefLists, rather than 3 HashMaps. Using sorted lists allows the implementation to reduce the in-memory footprint when storing many refs. Using specialized types for the elements allows the code to avoid additional map lookups for auxiliary stat information. To improve scan time during getRefs(), the lists are returned via a copy-on-write contract. Most callers of getRefs() do not modify the returned collections, so the copy-on-write semantics improves access on repositories with a large number of packed references. Iterator traversals of the returned Map<String,Ref> are performed using a simple merge-join of the two cache lists, ensuring we can perform the entire traversal in linear time as a function of the number of references: O(PackedRefs + LooseRefs). Scans of the loose reference space to update the cache run in O(LooseRefs log LooseRefs) time, as the directory contents are sorted before being merged against the in-memory cache. Since the majority of stable references are kept packed, there typically are only a handful of reference names to be sorted, so the sorting cost should not be very high. Locking is reduced during getRefs() by taking advantage of the copy-on-write semantics of the improved cache data structure. This permits concurrent readers to pull back references without blocking each other. If there is contention updating the cache during a scan, one or more updates are simply skipped and will get picked up again in a future scan. Writing to the $GIT_DIR/packed-refs during reference delete is now fully atomic. The file is locked, reparsed fresh, and written back out if a change is necessary. This avoids all race conditions with concurrent external updates of the packed-refs file. The RefLogWriter class has been fully folded into RefDirectory and is therefore deleted. Maintaining the reference's log is the responsiblity of the database implementation, and not all implementations will use java.io for access. Future work still remains to be done to abstract the ReflogReader class away from local disk IO. Change-Id: I26b9287c45a4b2d2be35ba2849daa316f5eec85d Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
Rewrite reference handling to be abstract and accurate This commit actually does three major changes to the way references are handled within JGit. Unfortunately they were easier to do as a single massive commit than to break them up into smaller units. Disambiguate symbolic references: --------------------------------- Reporting a symbolic reference such as HEAD as though it were any other normal reference like refs/heads/master causes subtle programming errors. We have been bitten by this error on several occasions, as have some downstream applications written by myself. Instead of reporting HEAD as a reference whose name differs from its "original name", report it as an actual SymbolicRef object that the application can test the type and examine the target of. With this change, Ref is now an abstract type with different subclasses for the different types. In the classical example of "HEAD" being a symbolic reference to branch "refs/heads/master", the Repository.getAllRefs() method will now return: Map<String, Ref> all = repository.getAllRefs(); SymbolicRef HEAD = (SymbolicRef) all.get("HEAD"); ObjectIdRef master = (ObjectIdRef) all.get("refs/heads/master"); assertSame(master, HEAD.getTarget()); assertSame(master.getObjectId(), HEAD.getObjectId()); assertEquals("HEAD", HEAD.getName()); assertEquals("refs/heads/master", master.getName()); A nice side-effect of this change is the storage type of the symbolic reference is no longer ambiguous with the storge type of the underlying reference it targets. In the above example, if master was only available in the packed-refs file, then the following is also true: assertSame(Ref.Storage.LOOSE, HEAD.getStorage()); assertSame(Ref.Storage.PACKED, master.getStorage()); (Prior to this change we returned the ambiguous storage of LOOSE_PACKED for HEAD, which was confusing since it wasn't actually true on disk). Another nice side-effect of this change is all intermediate symbolic references are preserved, and are therefore visible to the application when they walk the target chain. We can now correctly inspect chains of symbolic references. As a result of this change the Ref.getOrigName() method has been removed from the API. Applications should identify a symbolic reference by testing for isSymbolic() and not by using an arcane string comparsion between properties. Abstract the RefDatabase storage: --------------------------------- RefDatabase is now abstract, similar to ObjectDatabase, and a new concrete implementation called RefDirectory is used for the traditional on-disk storage layout. In the future we plan to support additional implementations, such as a pure in-memory RefDatabase for unit testing purposes. Optimize RefDirectory: ---------------------- The implementation of the in-memory reference cache, reading, and update routines has been completely rewritten. Much of the code was heavily borrowed or cribbed from the prior implementation, so copyright notices have been left intact as much as possible. The RefDirectory cache no longer confuses symbolic references with normal references. This permits the cache to resolve the value of a symbolic reference as late as possible, ensuring it is always current, without needing to maintain reverse pointers. The cache is now 2 sorted RefLists, rather than 3 HashMaps. Using sorted lists allows the implementation to reduce the in-memory footprint when storing many refs. Using specialized types for the elements allows the code to avoid additional map lookups for auxiliary stat information. To improve scan time during getRefs(), the lists are returned via a copy-on-write contract. Most callers of getRefs() do not modify the returned collections, so the copy-on-write semantics improves access on repositories with a large number of packed references. Iterator traversals of the returned Map<String,Ref> are performed using a simple merge-join of the two cache lists, ensuring we can perform the entire traversal in linear time as a function of the number of references: O(PackedRefs + LooseRefs). Scans of the loose reference space to update the cache run in O(LooseRefs log LooseRefs) time, as the directory contents are sorted before being merged against the in-memory cache. Since the majority of stable references are kept packed, there typically are only a handful of reference names to be sorted, so the sorting cost should not be very high. Locking is reduced during getRefs() by taking advantage of the copy-on-write semantics of the improved cache data structure. This permits concurrent readers to pull back references without blocking each other. If there is contention updating the cache during a scan, one or more updates are simply skipped and will get picked up again in a future scan. Writing to the $GIT_DIR/packed-refs during reference delete is now fully atomic. The file is locked, reparsed fresh, and written back out if a change is necessary. This avoids all race conditions with concurrent external updates of the packed-refs file. The RefLogWriter class has been fully folded into RefDirectory and is therefore deleted. Maintaining the reference's log is the responsiblity of the database implementation, and not all implementations will use java.io for access. Future work still remains to be done to abstract the ReflogReader class away from local disk IO. Change-Id: I26b9287c45a4b2d2be35ba2849daa316f5eec85d Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
Rewrite reference handling to be abstract and accurate This commit actually does three major changes to the way references are handled within JGit. Unfortunately they were easier to do as a single massive commit than to break them up into smaller units. Disambiguate symbolic references: --------------------------------- Reporting a symbolic reference such as HEAD as though it were any other normal reference like refs/heads/master causes subtle programming errors. We have been bitten by this error on several occasions, as have some downstream applications written by myself. Instead of reporting HEAD as a reference whose name differs from its "original name", report it as an actual SymbolicRef object that the application can test the type and examine the target of. With this change, Ref is now an abstract type with different subclasses for the different types. In the classical example of "HEAD" being a symbolic reference to branch "refs/heads/master", the Repository.getAllRefs() method will now return: Map<String, Ref> all = repository.getAllRefs(); SymbolicRef HEAD = (SymbolicRef) all.get("HEAD"); ObjectIdRef master = (ObjectIdRef) all.get("refs/heads/master"); assertSame(master, HEAD.getTarget()); assertSame(master.getObjectId(), HEAD.getObjectId()); assertEquals("HEAD", HEAD.getName()); assertEquals("refs/heads/master", master.getName()); A nice side-effect of this change is the storage type of the symbolic reference is no longer ambiguous with the storge type of the underlying reference it targets. In the above example, if master was only available in the packed-refs file, then the following is also true: assertSame(Ref.Storage.LOOSE, HEAD.getStorage()); assertSame(Ref.Storage.PACKED, master.getStorage()); (Prior to this change we returned the ambiguous storage of LOOSE_PACKED for HEAD, which was confusing since it wasn't actually true on disk). Another nice side-effect of this change is all intermediate symbolic references are preserved, and are therefore visible to the application when they walk the target chain. We can now correctly inspect chains of symbolic references. As a result of this change the Ref.getOrigName() method has been removed from the API. Applications should identify a symbolic reference by testing for isSymbolic() and not by using an arcane string comparsion between properties. Abstract the RefDatabase storage: --------------------------------- RefDatabase is now abstract, similar to ObjectDatabase, and a new concrete implementation called RefDirectory is used for the traditional on-disk storage layout. In the future we plan to support additional implementations, such as a pure in-memory RefDatabase for unit testing purposes. Optimize RefDirectory: ---------------------- The implementation of the in-memory reference cache, reading, and update routines has been completely rewritten. Much of the code was heavily borrowed or cribbed from the prior implementation, so copyright notices have been left intact as much as possible. The RefDirectory cache no longer confuses symbolic references with normal references. This permits the cache to resolve the value of a symbolic reference as late as possible, ensuring it is always current, without needing to maintain reverse pointers. The cache is now 2 sorted RefLists, rather than 3 HashMaps. Using sorted lists allows the implementation to reduce the in-memory footprint when storing many refs. Using specialized types for the elements allows the code to avoid additional map lookups for auxiliary stat information. To improve scan time during getRefs(), the lists are returned via a copy-on-write contract. Most callers of getRefs() do not modify the returned collections, so the copy-on-write semantics improves access on repositories with a large number of packed references. Iterator traversals of the returned Map<String,Ref> are performed using a simple merge-join of the two cache lists, ensuring we can perform the entire traversal in linear time as a function of the number of references: O(PackedRefs + LooseRefs). Scans of the loose reference space to update the cache run in O(LooseRefs log LooseRefs) time, as the directory contents are sorted before being merged against the in-memory cache. Since the majority of stable references are kept packed, there typically are only a handful of reference names to be sorted, so the sorting cost should not be very high. Locking is reduced during getRefs() by taking advantage of the copy-on-write semantics of the improved cache data structure. This permits concurrent readers to pull back references without blocking each other. If there is contention updating the cache during a scan, one or more updates are simply skipped and will get picked up again in a future scan. Writing to the $GIT_DIR/packed-refs during reference delete is now fully atomic. The file is locked, reparsed fresh, and written back out if a change is necessary. This avoids all race conditions with concurrent external updates of the packed-refs file. The RefLogWriter class has been fully folded into RefDirectory and is therefore deleted. Maintaining the reference's log is the responsiblity of the database implementation, and not all implementations will use java.io for access. Future work still remains to be done to abstract the ReflogReader class away from local disk IO. Change-Id: I26b9287c45a4b2d2be35ba2849daa316f5eec85d Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
Rewrite reference handling to be abstract and accurate This commit actually does three major changes to the way references are handled within JGit. Unfortunately they were easier to do as a single massive commit than to break them up into smaller units. Disambiguate symbolic references: --------------------------------- Reporting a symbolic reference such as HEAD as though it were any other normal reference like refs/heads/master causes subtle programming errors. We have been bitten by this error on several occasions, as have some downstream applications written by myself. Instead of reporting HEAD as a reference whose name differs from its "original name", report it as an actual SymbolicRef object that the application can test the type and examine the target of. With this change, Ref is now an abstract type with different subclasses for the different types. In the classical example of "HEAD" being a symbolic reference to branch "refs/heads/master", the Repository.getAllRefs() method will now return: Map<String, Ref> all = repository.getAllRefs(); SymbolicRef HEAD = (SymbolicRef) all.get("HEAD"); ObjectIdRef master = (ObjectIdRef) all.get("refs/heads/master"); assertSame(master, HEAD.getTarget()); assertSame(master.getObjectId(), HEAD.getObjectId()); assertEquals("HEAD", HEAD.getName()); assertEquals("refs/heads/master", master.getName()); A nice side-effect of this change is the storage type of the symbolic reference is no longer ambiguous with the storge type of the underlying reference it targets. In the above example, if master was only available in the packed-refs file, then the following is also true: assertSame(Ref.Storage.LOOSE, HEAD.getStorage()); assertSame(Ref.Storage.PACKED, master.getStorage()); (Prior to this change we returned the ambiguous storage of LOOSE_PACKED for HEAD, which was confusing since it wasn't actually true on disk). Another nice side-effect of this change is all intermediate symbolic references are preserved, and are therefore visible to the application when they walk the target chain. We can now correctly inspect chains of symbolic references. As a result of this change the Ref.getOrigName() method has been removed from the API. Applications should identify a symbolic reference by testing for isSymbolic() and not by using an arcane string comparsion between properties. Abstract the RefDatabase storage: --------------------------------- RefDatabase is now abstract, similar to ObjectDatabase, and a new concrete implementation called RefDirectory is used for the traditional on-disk storage layout. In the future we plan to support additional implementations, such as a pure in-memory RefDatabase for unit testing purposes. Optimize RefDirectory: ---------------------- The implementation of the in-memory reference cache, reading, and update routines has been completely rewritten. Much of the code was heavily borrowed or cribbed from the prior implementation, so copyright notices have been left intact as much as possible. The RefDirectory cache no longer confuses symbolic references with normal references. This permits the cache to resolve the value of a symbolic reference as late as possible, ensuring it is always current, without needing to maintain reverse pointers. The cache is now 2 sorted RefLists, rather than 3 HashMaps. Using sorted lists allows the implementation to reduce the in-memory footprint when storing many refs. Using specialized types for the elements allows the code to avoid additional map lookups for auxiliary stat information. To improve scan time during getRefs(), the lists are returned via a copy-on-write contract. Most callers of getRefs() do not modify the returned collections, so the copy-on-write semantics improves access on repositories with a large number of packed references. Iterator traversals of the returned Map<String,Ref> are performed using a simple merge-join of the two cache lists, ensuring we can perform the entire traversal in linear time as a function of the number of references: O(PackedRefs + LooseRefs). Scans of the loose reference space to update the cache run in O(LooseRefs log LooseRefs) time, as the directory contents are sorted before being merged against the in-memory cache. Since the majority of stable references are kept packed, there typically are only a handful of reference names to be sorted, so the sorting cost should not be very high. Locking is reduced during getRefs() by taking advantage of the copy-on-write semantics of the improved cache data structure. This permits concurrent readers to pull back references without blocking each other. If there is contention updating the cache during a scan, one or more updates are simply skipped and will get picked up again in a future scan. Writing to the $GIT_DIR/packed-refs during reference delete is now fully atomic. The file is locked, reparsed fresh, and written back out if a change is necessary. This avoids all race conditions with concurrent external updates of the packed-refs file. The RefLogWriter class has been fully folded into RefDirectory and is therefore deleted. Maintaining the reference's log is the responsiblity of the database implementation, and not all implementations will use java.io for access. Future work still remains to be done to abstract the ReflogReader class away from local disk IO. Change-Id: I26b9287c45a4b2d2be35ba2849daa316f5eec85d Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
Rewrite reference handling to be abstract and accurate This commit actually does three major changes to the way references are handled within JGit. Unfortunately they were easier to do as a single massive commit than to break them up into smaller units. Disambiguate symbolic references: --------------------------------- Reporting a symbolic reference such as HEAD as though it were any other normal reference like refs/heads/master causes subtle programming errors. We have been bitten by this error on several occasions, as have some downstream applications written by myself. Instead of reporting HEAD as a reference whose name differs from its "original name", report it as an actual SymbolicRef object that the application can test the type and examine the target of. With this change, Ref is now an abstract type with different subclasses for the different types. In the classical example of "HEAD" being a symbolic reference to branch "refs/heads/master", the Repository.getAllRefs() method will now return: Map<String, Ref> all = repository.getAllRefs(); SymbolicRef HEAD = (SymbolicRef) all.get("HEAD"); ObjectIdRef master = (ObjectIdRef) all.get("refs/heads/master"); assertSame(master, HEAD.getTarget()); assertSame(master.getObjectId(), HEAD.getObjectId()); assertEquals("HEAD", HEAD.getName()); assertEquals("refs/heads/master", master.getName()); A nice side-effect of this change is the storage type of the symbolic reference is no longer ambiguous with the storge type of the underlying reference it targets. In the above example, if master was only available in the packed-refs file, then the following is also true: assertSame(Ref.Storage.LOOSE, HEAD.getStorage()); assertSame(Ref.Storage.PACKED, master.getStorage()); (Prior to this change we returned the ambiguous storage of LOOSE_PACKED for HEAD, which was confusing since it wasn't actually true on disk). Another nice side-effect of this change is all intermediate symbolic references are preserved, and are therefore visible to the application when they walk the target chain. We can now correctly inspect chains of symbolic references. As a result of this change the Ref.getOrigName() method has been removed from the API. Applications should identify a symbolic reference by testing for isSymbolic() and not by using an arcane string comparsion between properties. Abstract the RefDatabase storage: --------------------------------- RefDatabase is now abstract, similar to ObjectDatabase, and a new concrete implementation called RefDirectory is used for the traditional on-disk storage layout. In the future we plan to support additional implementations, such as a pure in-memory RefDatabase for unit testing purposes. Optimize RefDirectory: ---------------------- The implementation of the in-memory reference cache, reading, and update routines has been completely rewritten. Much of the code was heavily borrowed or cribbed from the prior implementation, so copyright notices have been left intact as much as possible. The RefDirectory cache no longer confuses symbolic references with normal references. This permits the cache to resolve the value of a symbolic reference as late as possible, ensuring it is always current, without needing to maintain reverse pointers. The cache is now 2 sorted RefLists, rather than 3 HashMaps. Using sorted lists allows the implementation to reduce the in-memory footprint when storing many refs. Using specialized types for the elements allows the code to avoid additional map lookups for auxiliary stat information. To improve scan time during getRefs(), the lists are returned via a copy-on-write contract. Most callers of getRefs() do not modify the returned collections, so the copy-on-write semantics improves access on repositories with a large number of packed references. Iterator traversals of the returned Map<String,Ref> are performed using a simple merge-join of the two cache lists, ensuring we can perform the entire traversal in linear time as a function of the number of references: O(PackedRefs + LooseRefs). Scans of the loose reference space to update the cache run in O(LooseRefs log LooseRefs) time, as the directory contents are sorted before being merged against the in-memory cache. Since the majority of stable references are kept packed, there typically are only a handful of reference names to be sorted, so the sorting cost should not be very high. Locking is reduced during getRefs() by taking advantage of the copy-on-write semantics of the improved cache data structure. This permits concurrent readers to pull back references without blocking each other. If there is contention updating the cache during a scan, one or more updates are simply skipped and will get picked up again in a future scan. Writing to the $GIT_DIR/packed-refs during reference delete is now fully atomic. The file is locked, reparsed fresh, and written back out if a change is necessary. This avoids all race conditions with concurrent external updates of the packed-refs file. The RefLogWriter class has been fully folded into RefDirectory and is therefore deleted. Maintaining the reference's log is the responsiblity of the database implementation, and not all implementations will use java.io for access. Future work still remains to be done to abstract the ReflogReader class away from local disk IO. Change-Id: I26b9287c45a4b2d2be35ba2849daa316f5eec85d Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
Rewrite reference handling to be abstract and accurate This commit actually does three major changes to the way references are handled within JGit. Unfortunately they were easier to do as a single massive commit than to break them up into smaller units. Disambiguate symbolic references: --------------------------------- Reporting a symbolic reference such as HEAD as though it were any other normal reference like refs/heads/master causes subtle programming errors. We have been bitten by this error on several occasions, as have some downstream applications written by myself. Instead of reporting HEAD as a reference whose name differs from its "original name", report it as an actual SymbolicRef object that the application can test the type and examine the target of. With this change, Ref is now an abstract type with different subclasses for the different types. In the classical example of "HEAD" being a symbolic reference to branch "refs/heads/master", the Repository.getAllRefs() method will now return: Map<String, Ref> all = repository.getAllRefs(); SymbolicRef HEAD = (SymbolicRef) all.get("HEAD"); ObjectIdRef master = (ObjectIdRef) all.get("refs/heads/master"); assertSame(master, HEAD.getTarget()); assertSame(master.getObjectId(), HEAD.getObjectId()); assertEquals("HEAD", HEAD.getName()); assertEquals("refs/heads/master", master.getName()); A nice side-effect of this change is the storage type of the symbolic reference is no longer ambiguous with the storge type of the underlying reference it targets. In the above example, if master was only available in the packed-refs file, then the following is also true: assertSame(Ref.Storage.LOOSE, HEAD.getStorage()); assertSame(Ref.Storage.PACKED, master.getStorage()); (Prior to this change we returned the ambiguous storage of LOOSE_PACKED for HEAD, which was confusing since it wasn't actually true on disk). Another nice side-effect of this change is all intermediate symbolic references are preserved, and are therefore visible to the application when they walk the target chain. We can now correctly inspect chains of symbolic references. As a result of this change the Ref.getOrigName() method has been removed from the API. Applications should identify a symbolic reference by testing for isSymbolic() and not by using an arcane string comparsion between properties. Abstract the RefDatabase storage: --------------------------------- RefDatabase is now abstract, similar to ObjectDatabase, and a new concrete implementation called RefDirectory is used for the traditional on-disk storage layout. In the future we plan to support additional implementations, such as a pure in-memory RefDatabase for unit testing purposes. Optimize RefDirectory: ---------------------- The implementation of the in-memory reference cache, reading, and update routines has been completely rewritten. Much of the code was heavily borrowed or cribbed from the prior implementation, so copyright notices have been left intact as much as possible. The RefDirectory cache no longer confuses symbolic references with normal references. This permits the cache to resolve the value of a symbolic reference as late as possible, ensuring it is always current, without needing to maintain reverse pointers. The cache is now 2 sorted RefLists, rather than 3 HashMaps. Using sorted lists allows the implementation to reduce the in-memory footprint when storing many refs. Using specialized types for the elements allows the code to avoid additional map lookups for auxiliary stat information. To improve scan time during getRefs(), the lists are returned via a copy-on-write contract. Most callers of getRefs() do not modify the returned collections, so the copy-on-write semantics improves access on repositories with a large number of packed references. Iterator traversals of the returned Map<String,Ref> are performed using a simple merge-join of the two cache lists, ensuring we can perform the entire traversal in linear time as a function of the number of references: O(PackedRefs + LooseRefs). Scans of the loose reference space to update the cache run in O(LooseRefs log LooseRefs) time, as the directory contents are sorted before being merged against the in-memory cache. Since the majority of stable references are kept packed, there typically are only a handful of reference names to be sorted, so the sorting cost should not be very high. Locking is reduced during getRefs() by taking advantage of the copy-on-write semantics of the improved cache data structure. This permits concurrent readers to pull back references without blocking each other. If there is contention updating the cache during a scan, one or more updates are simply skipped and will get picked up again in a future scan. Writing to the $GIT_DIR/packed-refs during reference delete is now fully atomic. The file is locked, reparsed fresh, and written back out if a change is necessary. This avoids all race conditions with concurrent external updates of the packed-refs file. The RefLogWriter class has been fully folded into RefDirectory and is therefore deleted. Maintaining the reference's log is the responsiblity of the database implementation, and not all implementations will use java.io for access. Future work still remains to be done to abstract the ReflogReader class away from local disk IO. Change-Id: I26b9287c45a4b2d2be35ba2849daa316f5eec85d Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
Rewrite reference handling to be abstract and accurate This commit actually does three major changes to the way references are handled within JGit. Unfortunately they were easier to do as a single massive commit than to break them up into smaller units. Disambiguate symbolic references: --------------------------------- Reporting a symbolic reference such as HEAD as though it were any other normal reference like refs/heads/master causes subtle programming errors. We have been bitten by this error on several occasions, as have some downstream applications written by myself. Instead of reporting HEAD as a reference whose name differs from its "original name", report it as an actual SymbolicRef object that the application can test the type and examine the target of. With this change, Ref is now an abstract type with different subclasses for the different types. In the classical example of "HEAD" being a symbolic reference to branch "refs/heads/master", the Repository.getAllRefs() method will now return: Map<String, Ref> all = repository.getAllRefs(); SymbolicRef HEAD = (SymbolicRef) all.get("HEAD"); ObjectIdRef master = (ObjectIdRef) all.get("refs/heads/master"); assertSame(master, HEAD.getTarget()); assertSame(master.getObjectId(), HEAD.getObjectId()); assertEquals("HEAD", HEAD.getName()); assertEquals("refs/heads/master", master.getName()); A nice side-effect of this change is the storage type of the symbolic reference is no longer ambiguous with the storge type of the underlying reference it targets. In the above example, if master was only available in the packed-refs file, then the following is also true: assertSame(Ref.Storage.LOOSE, HEAD.getStorage()); assertSame(Ref.Storage.PACKED, master.getStorage()); (Prior to this change we returned the ambiguous storage of LOOSE_PACKED for HEAD, which was confusing since it wasn't actually true on disk). Another nice side-effect of this change is all intermediate symbolic references are preserved, and are therefore visible to the application when they walk the target chain. We can now correctly inspect chains of symbolic references. As a result of this change the Ref.getOrigName() method has been removed from the API. Applications should identify a symbolic reference by testing for isSymbolic() and not by using an arcane string comparsion between properties. Abstract the RefDatabase storage: --------------------------------- RefDatabase is now abstract, similar to ObjectDatabase, and a new concrete implementation called RefDirectory is used for the traditional on-disk storage layout. In the future we plan to support additional implementations, such as a pure in-memory RefDatabase for unit testing purposes. Optimize RefDirectory: ---------------------- The implementation of the in-memory reference cache, reading, and update routines has been completely rewritten. Much of the code was heavily borrowed or cribbed from the prior implementation, so copyright notices have been left intact as much as possible. The RefDirectory cache no longer confuses symbolic references with normal references. This permits the cache to resolve the value of a symbolic reference as late as possible, ensuring it is always current, without needing to maintain reverse pointers. The cache is now 2 sorted RefLists, rather than 3 HashMaps. Using sorted lists allows the implementation to reduce the in-memory footprint when storing many refs. Using specialized types for the elements allows the code to avoid additional map lookups for auxiliary stat information. To improve scan time during getRefs(), the lists are returned via a copy-on-write contract. Most callers of getRefs() do not modify the returned collections, so the copy-on-write semantics improves access on repositories with a large number of packed references. Iterator traversals of the returned Map<String,Ref> are performed using a simple merge-join of the two cache lists, ensuring we can perform the entire traversal in linear time as a function of the number of references: O(PackedRefs + LooseRefs). Scans of the loose reference space to update the cache run in O(LooseRefs log LooseRefs) time, as the directory contents are sorted before being merged against the in-memory cache. Since the majority of stable references are kept packed, there typically are only a handful of reference names to be sorted, so the sorting cost should not be very high. Locking is reduced during getRefs() by taking advantage of the copy-on-write semantics of the improved cache data structure. This permits concurrent readers to pull back references without blocking each other. If there is contention updating the cache during a scan, one or more updates are simply skipped and will get picked up again in a future scan. Writing to the $GIT_DIR/packed-refs during reference delete is now fully atomic. The file is locked, reparsed fresh, and written back out if a change is necessary. This avoids all race conditions with concurrent external updates of the packed-refs file. The RefLogWriter class has been fully folded into RefDirectory and is therefore deleted. Maintaining the reference's log is the responsiblity of the database implementation, and not all implementations will use java.io for access. Future work still remains to be done to abstract the ReflogReader class away from local disk IO. Change-Id: I26b9287c45a4b2d2be35ba2849daa316f5eec85d Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
Rewrite reference handling to be abstract and accurate This commit actually does three major changes to the way references are handled within JGit. Unfortunately they were easier to do as a single massive commit than to break them up into smaller units. Disambiguate symbolic references: --------------------------------- Reporting a symbolic reference such as HEAD as though it were any other normal reference like refs/heads/master causes subtle programming errors. We have been bitten by this error on several occasions, as have some downstream applications written by myself. Instead of reporting HEAD as a reference whose name differs from its "original name", report it as an actual SymbolicRef object that the application can test the type and examine the target of. With this change, Ref is now an abstract type with different subclasses for the different types. In the classical example of "HEAD" being a symbolic reference to branch "refs/heads/master", the Repository.getAllRefs() method will now return: Map<String, Ref> all = repository.getAllRefs(); SymbolicRef HEAD = (SymbolicRef) all.get("HEAD"); ObjectIdRef master = (ObjectIdRef) all.get("refs/heads/master"); assertSame(master, HEAD.getTarget()); assertSame(master.getObjectId(), HEAD.getObjectId()); assertEquals("HEAD", HEAD.getName()); assertEquals("refs/heads/master", master.getName()); A nice side-effect of this change is the storage type of the symbolic reference is no longer ambiguous with the storge type of the underlying reference it targets. In the above example, if master was only available in the packed-refs file, then the following is also true: assertSame(Ref.Storage.LOOSE, HEAD.getStorage()); assertSame(Ref.Storage.PACKED, master.getStorage()); (Prior to this change we returned the ambiguous storage of LOOSE_PACKED for HEAD, which was confusing since it wasn't actually true on disk). Another nice side-effect of this change is all intermediate symbolic references are preserved, and are therefore visible to the application when they walk the target chain. We can now correctly inspect chains of symbolic references. As a result of this change the Ref.getOrigName() method has been removed from the API. Applications should identify a symbolic reference by testing for isSymbolic() and not by using an arcane string comparsion between properties. Abstract the RefDatabase storage: --------------------------------- RefDatabase is now abstract, similar to ObjectDatabase, and a new concrete implementation called RefDirectory is used for the traditional on-disk storage layout. In the future we plan to support additional implementations, such as a pure in-memory RefDatabase for unit testing purposes. Optimize RefDirectory: ---------------------- The implementation of the in-memory reference cache, reading, and update routines has been completely rewritten. Much of the code was heavily borrowed or cribbed from the prior implementation, so copyright notices have been left intact as much as possible. The RefDirectory cache no longer confuses symbolic references with normal references. This permits the cache to resolve the value of a symbolic reference as late as possible, ensuring it is always current, without needing to maintain reverse pointers. The cache is now 2 sorted RefLists, rather than 3 HashMaps. Using sorted lists allows the implementation to reduce the in-memory footprint when storing many refs. Using specialized types for the elements allows the code to avoid additional map lookups for auxiliary stat information. To improve scan time during getRefs(), the lists are returned via a copy-on-write contract. Most callers of getRefs() do not modify the returned collections, so the copy-on-write semantics improves access on repositories with a large number of packed references. Iterator traversals of the returned Map<String,Ref> are performed using a simple merge-join of the two cache lists, ensuring we can perform the entire traversal in linear time as a function of the number of references: O(PackedRefs + LooseRefs). Scans of the loose reference space to update the cache run in O(LooseRefs log LooseRefs) time, as the directory contents are sorted before being merged against the in-memory cache. Since the majority of stable references are kept packed, there typically are only a handful of reference names to be sorted, so the sorting cost should not be very high. Locking is reduced during getRefs() by taking advantage of the copy-on-write semantics of the improved cache data structure. This permits concurrent readers to pull back references without blocking each other. If there is contention updating the cache during a scan, one or more updates are simply skipped and will get picked up again in a future scan. Writing to the $GIT_DIR/packed-refs during reference delete is now fully atomic. The file is locked, reparsed fresh, and written back out if a change is necessary. This avoids all race conditions with concurrent external updates of the packed-refs file. The RefLogWriter class has been fully folded into RefDirectory and is therefore deleted. Maintaining the reference's log is the responsiblity of the database implementation, and not all implementations will use java.io for access. Future work still remains to be done to abstract the ReflogReader class away from local disk IO. Change-Id: I26b9287c45a4b2d2be35ba2849daa316f5eec85d Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
Rewrite reference handling to be abstract and accurate This commit actually does three major changes to the way references are handled within JGit. Unfortunately they were easier to do as a single massive commit than to break them up into smaller units. Disambiguate symbolic references: --------------------------------- Reporting a symbolic reference such as HEAD as though it were any other normal reference like refs/heads/master causes subtle programming errors. We have been bitten by this error on several occasions, as have some downstream applications written by myself. Instead of reporting HEAD as a reference whose name differs from its "original name", report it as an actual SymbolicRef object that the application can test the type and examine the target of. With this change, Ref is now an abstract type with different subclasses for the different types. In the classical example of "HEAD" being a symbolic reference to branch "refs/heads/master", the Repository.getAllRefs() method will now return: Map<String, Ref> all = repository.getAllRefs(); SymbolicRef HEAD = (SymbolicRef) all.get("HEAD"); ObjectIdRef master = (ObjectIdRef) all.get("refs/heads/master"); assertSame(master, HEAD.getTarget()); assertSame(master.getObjectId(), HEAD.getObjectId()); assertEquals("HEAD", HEAD.getName()); assertEquals("refs/heads/master", master.getName()); A nice side-effect of this change is the storage type of the symbolic reference is no longer ambiguous with the storge type of the underlying reference it targets. In the above example, if master was only available in the packed-refs file, then the following is also true: assertSame(Ref.Storage.LOOSE, HEAD.getStorage()); assertSame(Ref.Storage.PACKED, master.getStorage()); (Prior to this change we returned the ambiguous storage of LOOSE_PACKED for HEAD, which was confusing since it wasn't actually true on disk). Another nice side-effect of this change is all intermediate symbolic references are preserved, and are therefore visible to the application when they walk the target chain. We can now correctly inspect chains of symbolic references. As a result of this change the Ref.getOrigName() method has been removed from the API. Applications should identify a symbolic reference by testing for isSymbolic() and not by using an arcane string comparsion between properties. Abstract the RefDatabase storage: --------------------------------- RefDatabase is now abstract, similar to ObjectDatabase, and a new concrete implementation called RefDirectory is used for the traditional on-disk storage layout. In the future we plan to support additional implementations, such as a pure in-memory RefDatabase for unit testing purposes. Optimize RefDirectory: ---------------------- The implementation of the in-memory reference cache, reading, and update routines has been completely rewritten. Much of the code was heavily borrowed or cribbed from the prior implementation, so copyright notices have been left intact as much as possible. The RefDirectory cache no longer confuses symbolic references with normal references. This permits the cache to resolve the value of a symbolic reference as late as possible, ensuring it is always current, without needing to maintain reverse pointers. The cache is now 2 sorted RefLists, rather than 3 HashMaps. Using sorted lists allows the implementation to reduce the in-memory footprint when storing many refs. Using specialized types for the elements allows the code to avoid additional map lookups for auxiliary stat information. To improve scan time during getRefs(), the lists are returned via a copy-on-write contract. Most callers of getRefs() do not modify the returned collections, so the copy-on-write semantics improves access on repositories with a large number of packed references. Iterator traversals of the returned Map<String,Ref> are performed using a simple merge-join of the two cache lists, ensuring we can perform the entire traversal in linear time as a function of the number of references: O(PackedRefs + LooseRefs). Scans of the loose reference space to update the cache run in O(LooseRefs log LooseRefs) time, as the directory contents are sorted before being merged against the in-memory cache. Since the majority of stable references are kept packed, there typically are only a handful of reference names to be sorted, so the sorting cost should not be very high. Locking is reduced during getRefs() by taking advantage of the copy-on-write semantics of the improved cache data structure. This permits concurrent readers to pull back references without blocking each other. If there is contention updating the cache during a scan, one or more updates are simply skipped and will get picked up again in a future scan. Writing to the $GIT_DIR/packed-refs during reference delete is now fully atomic. The file is locked, reparsed fresh, and written back out if a change is necessary. This avoids all race conditions with concurrent external updates of the packed-refs file. The RefLogWriter class has been fully folded into RefDirectory and is therefore deleted. Maintaining the reference's log is the responsiblity of the database implementation, and not all implementations will use java.io for access. Future work still remains to be done to abstract the ReflogReader class away from local disk IO. Change-Id: I26b9287c45a4b2d2be35ba2849daa316f5eec85d Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
Rewrite reference handling to be abstract and accurate This commit actually does three major changes to the way references are handled within JGit. Unfortunately they were easier to do as a single massive commit than to break them up into smaller units. Disambiguate symbolic references: --------------------------------- Reporting a symbolic reference such as HEAD as though it were any other normal reference like refs/heads/master causes subtle programming errors. We have been bitten by this error on several occasions, as have some downstream applications written by myself. Instead of reporting HEAD as a reference whose name differs from its "original name", report it as an actual SymbolicRef object that the application can test the type and examine the target of. With this change, Ref is now an abstract type with different subclasses for the different types. In the classical example of "HEAD" being a symbolic reference to branch "refs/heads/master", the Repository.getAllRefs() method will now return: Map<String, Ref> all = repository.getAllRefs(); SymbolicRef HEAD = (SymbolicRef) all.get("HEAD"); ObjectIdRef master = (ObjectIdRef) all.get("refs/heads/master"); assertSame(master, HEAD.getTarget()); assertSame(master.getObjectId(), HEAD.getObjectId()); assertEquals("HEAD", HEAD.getName()); assertEquals("refs/heads/master", master.getName()); A nice side-effect of this change is the storage type of the symbolic reference is no longer ambiguous with the storge type of the underlying reference it targets. In the above example, if master was only available in the packed-refs file, then the following is also true: assertSame(Ref.Storage.LOOSE, HEAD.getStorage()); assertSame(Ref.Storage.PACKED, master.getStorage()); (Prior to this change we returned the ambiguous storage of LOOSE_PACKED for HEAD, which was confusing since it wasn't actually true on disk). Another nice side-effect of this change is all intermediate symbolic references are preserved, and are therefore visible to the application when they walk the target chain. We can now correctly inspect chains of symbolic references. As a result of this change the Ref.getOrigName() method has been removed from the API. Applications should identify a symbolic reference by testing for isSymbolic() and not by using an arcane string comparsion between properties. Abstract the RefDatabase storage: --------------------------------- RefDatabase is now abstract, similar to ObjectDatabase, and a new concrete implementation called RefDirectory is used for the traditional on-disk storage layout. In the future we plan to support additional implementations, such as a pure in-memory RefDatabase for unit testing purposes. Optimize RefDirectory: ---------------------- The implementation of the in-memory reference cache, reading, and update routines has been completely rewritten. Much of the code was heavily borrowed or cribbed from the prior implementation, so copyright notices have been left intact as much as possible. The RefDirectory cache no longer confuses symbolic references with normal references. This permits the cache to resolve the value of a symbolic reference as late as possible, ensuring it is always current, without needing to maintain reverse pointers. The cache is now 2 sorted RefLists, rather than 3 HashMaps. Using sorted lists allows the implementation to reduce the in-memory footprint when storing many refs. Using specialized types for the elements allows the code to avoid additional map lookups for auxiliary stat information. To improve scan time during getRefs(), the lists are returned via a copy-on-write contract. Most callers of getRefs() do not modify the returned collections, so the copy-on-write semantics improves access on repositories with a large number of packed references. Iterator traversals of the returned Map<String,Ref> are performed using a simple merge-join of the two cache lists, ensuring we can perform the entire traversal in linear time as a function of the number of references: O(PackedRefs + LooseRefs). Scans of the loose reference space to update the cache run in O(LooseRefs log LooseRefs) time, as the directory contents are sorted before being merged against the in-memory cache. Since the majority of stable references are kept packed, there typically are only a handful of reference names to be sorted, so the sorting cost should not be very high. Locking is reduced during getRefs() by taking advantage of the copy-on-write semantics of the improved cache data structure. This permits concurrent readers to pull back references without blocking each other. If there is contention updating the cache during a scan, one or more updates are simply skipped and will get picked up again in a future scan. Writing to the $GIT_DIR/packed-refs during reference delete is now fully atomic. The file is locked, reparsed fresh, and written back out if a change is necessary. This avoids all race conditions with concurrent external updates of the packed-refs file. The RefLogWriter class has been fully folded into RefDirectory and is therefore deleted. Maintaining the reference's log is the responsiblity of the database implementation, and not all implementations will use java.io for access. Future work still remains to be done to abstract the ReflogReader class away from local disk IO. Change-Id: I26b9287c45a4b2d2be35ba2849daa316f5eec85d Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
Rewrite reference handling to be abstract and accurate This commit actually does three major changes to the way references are handled within JGit. Unfortunately they were easier to do as a single massive commit than to break them up into smaller units. Disambiguate symbolic references: --------------------------------- Reporting a symbolic reference such as HEAD as though it were any other normal reference like refs/heads/master causes subtle programming errors. We have been bitten by this error on several occasions, as have some downstream applications written by myself. Instead of reporting HEAD as a reference whose name differs from its "original name", report it as an actual SymbolicRef object that the application can test the type and examine the target of. With this change, Ref is now an abstract type with different subclasses for the different types. In the classical example of "HEAD" being a symbolic reference to branch "refs/heads/master", the Repository.getAllRefs() method will now return: Map<String, Ref> all = repository.getAllRefs(); SymbolicRef HEAD = (SymbolicRef) all.get("HEAD"); ObjectIdRef master = (ObjectIdRef) all.get("refs/heads/master"); assertSame(master, HEAD.getTarget()); assertSame(master.getObjectId(), HEAD.getObjectId()); assertEquals("HEAD", HEAD.getName()); assertEquals("refs/heads/master", master.getName()); A nice side-effect of this change is the storage type of the symbolic reference is no longer ambiguous with the storge type of the underlying reference it targets. In the above example, if master was only available in the packed-refs file, then the following is also true: assertSame(Ref.Storage.LOOSE, HEAD.getStorage()); assertSame(Ref.Storage.PACKED, master.getStorage()); (Prior to this change we returned the ambiguous storage of LOOSE_PACKED for HEAD, which was confusing since it wasn't actually true on disk). Another nice side-effect of this change is all intermediate symbolic references are preserved, and are therefore visible to the application when they walk the target chain. We can now correctly inspect chains of symbolic references. As a result of this change the Ref.getOrigName() method has been removed from the API. Applications should identify a symbolic reference by testing for isSymbolic() and not by using an arcane string comparsion between properties. Abstract the RefDatabase storage: --------------------------------- RefDatabase is now abstract, similar to ObjectDatabase, and a new concrete implementation called RefDirectory is used for the traditional on-disk storage layout. In the future we plan to support additional implementations, such as a pure in-memory RefDatabase for unit testing purposes. Optimize RefDirectory: ---------------------- The implementation of the in-memory reference cache, reading, and update routines has been completely rewritten. Much of the code was heavily borrowed or cribbed from the prior implementation, so copyright notices have been left intact as much as possible. The RefDirectory cache no longer confuses symbolic references with normal references. This permits the cache to resolve the value of a symbolic reference as late as possible, ensuring it is always current, without needing to maintain reverse pointers. The cache is now 2 sorted RefLists, rather than 3 HashMaps. Using sorted lists allows the implementation to reduce the in-memory footprint when storing many refs. Using specialized types for the elements allows the code to avoid additional map lookups for auxiliary stat information. To improve scan time during getRefs(), the lists are returned via a copy-on-write contract. Most callers of getRefs() do not modify the returned collections, so the copy-on-write semantics improves access on repositories with a large number of packed references. Iterator traversals of the returned Map<String,Ref> are performed using a simple merge-join of the two cache lists, ensuring we can perform the entire traversal in linear time as a function of the number of references: O(PackedRefs + LooseRefs). Scans of the loose reference space to update the cache run in O(LooseRefs log LooseRefs) time, as the directory contents are sorted before being merged against the in-memory cache. Since the majority of stable references are kept packed, there typically are only a handful of reference names to be sorted, so the sorting cost should not be very high. Locking is reduced during getRefs() by taking advantage of the copy-on-write semantics of the improved cache data structure. This permits concurrent readers to pull back references without blocking each other. If there is contention updating the cache during a scan, one or more updates are simply skipped and will get picked up again in a future scan. Writing to the $GIT_DIR/packed-refs during reference delete is now fully atomic. The file is locked, reparsed fresh, and written back out if a change is necessary. This avoids all race conditions with concurrent external updates of the packed-refs file. The RefLogWriter class has been fully folded into RefDirectory and is therefore deleted. Maintaining the reference's log is the responsiblity of the database implementation, and not all implementations will use java.io for access. Future work still remains to be done to abstract the ReflogReader class away from local disk IO. Change-Id: I26b9287c45a4b2d2be35ba2849daa316f5eec85d Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
Rewrite reference handling to be abstract and accurate This commit actually does three major changes to the way references are handled within JGit. Unfortunately they were easier to do as a single massive commit than to break them up into smaller units. Disambiguate symbolic references: --------------------------------- Reporting a symbolic reference such as HEAD as though it were any other normal reference like refs/heads/master causes subtle programming errors. We have been bitten by this error on several occasions, as have some downstream applications written by myself. Instead of reporting HEAD as a reference whose name differs from its "original name", report it as an actual SymbolicRef object that the application can test the type and examine the target of. With this change, Ref is now an abstract type with different subclasses for the different types. In the classical example of "HEAD" being a symbolic reference to branch "refs/heads/master", the Repository.getAllRefs() method will now return: Map<String, Ref> all = repository.getAllRefs(); SymbolicRef HEAD = (SymbolicRef) all.get("HEAD"); ObjectIdRef master = (ObjectIdRef) all.get("refs/heads/master"); assertSame(master, HEAD.getTarget()); assertSame(master.getObjectId(), HEAD.getObjectId()); assertEquals("HEAD", HEAD.getName()); assertEquals("refs/heads/master", master.getName()); A nice side-effect of this change is the storage type of the symbolic reference is no longer ambiguous with the storge type of the underlying reference it targets. In the above example, if master was only available in the packed-refs file, then the following is also true: assertSame(Ref.Storage.LOOSE, HEAD.getStorage()); assertSame(Ref.Storage.PACKED, master.getStorage()); (Prior to this change we returned the ambiguous storage of LOOSE_PACKED for HEAD, which was confusing since it wasn't actually true on disk). Another nice side-effect of this change is all intermediate symbolic references are preserved, and are therefore visible to the application when they walk the target chain. We can now correctly inspect chains of symbolic references. As a result of this change the Ref.getOrigName() method has been removed from the API. Applications should identify a symbolic reference by testing for isSymbolic() and not by using an arcane string comparsion between properties. Abstract the RefDatabase storage: --------------------------------- RefDatabase is now abstract, similar to ObjectDatabase, and a new concrete implementation called RefDirectory is used for the traditional on-disk storage layout. In the future we plan to support additional implementations, such as a pure in-memory RefDatabase for unit testing purposes. Optimize RefDirectory: ---------------------- The implementation of the in-memory reference cache, reading, and update routines has been completely rewritten. Much of the code was heavily borrowed or cribbed from the prior implementation, so copyright notices have been left intact as much as possible. The RefDirectory cache no longer confuses symbolic references with normal references. This permits the cache to resolve the value of a symbolic reference as late as possible, ensuring it is always current, without needing to maintain reverse pointers. The cache is now 2 sorted RefLists, rather than 3 HashMaps. Using sorted lists allows the implementation to reduce the in-memory footprint when storing many refs. Using specialized types for the elements allows the code to avoid additional map lookups for auxiliary stat information. To improve scan time during getRefs(), the lists are returned via a copy-on-write contract. Most callers of getRefs() do not modify the returned collections, so the copy-on-write semantics improves access on repositories with a large number of packed references. Iterator traversals of the returned Map<String,Ref> are performed using a simple merge-join of the two cache lists, ensuring we can perform the entire traversal in linear time as a function of the number of references: O(PackedRefs + LooseRefs). Scans of the loose reference space to update the cache run in O(LooseRefs log LooseRefs) time, as the directory contents are sorted before being merged against the in-memory cache. Since the majority of stable references are kept packed, there typically are only a handful of reference names to be sorted, so the sorting cost should not be very high. Locking is reduced during getRefs() by taking advantage of the copy-on-write semantics of the improved cache data structure. This permits concurrent readers to pull back references without blocking each other. If there is contention updating the cache during a scan, one or more updates are simply skipped and will get picked up again in a future scan. Writing to the $GIT_DIR/packed-refs during reference delete is now fully atomic. The file is locked, reparsed fresh, and written back out if a change is necessary. This avoids all race conditions with concurrent external updates of the packed-refs file. The RefLogWriter class has been fully folded into RefDirectory and is therefore deleted. Maintaining the reference's log is the responsiblity of the database implementation, and not all implementations will use java.io for access. Future work still remains to be done to abstract the ReflogReader class away from local disk IO. Change-Id: I26b9287c45a4b2d2be35ba2849daa316f5eec85d Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
Rewrite reference handling to be abstract and accurate This commit actually does three major changes to the way references are handled within JGit. Unfortunately they were easier to do as a single massive commit than to break them up into smaller units. Disambiguate symbolic references: --------------------------------- Reporting a symbolic reference such as HEAD as though it were any other normal reference like refs/heads/master causes subtle programming errors. We have been bitten by this error on several occasions, as have some downstream applications written by myself. Instead of reporting HEAD as a reference whose name differs from its "original name", report it as an actual SymbolicRef object that the application can test the type and examine the target of. With this change, Ref is now an abstract type with different subclasses for the different types. In the classical example of "HEAD" being a symbolic reference to branch "refs/heads/master", the Repository.getAllRefs() method will now return: Map<String, Ref> all = repository.getAllRefs(); SymbolicRef HEAD = (SymbolicRef) all.get("HEAD"); ObjectIdRef master = (ObjectIdRef) all.get("refs/heads/master"); assertSame(master, HEAD.getTarget()); assertSame(master.getObjectId(), HEAD.getObjectId()); assertEquals("HEAD", HEAD.getName()); assertEquals("refs/heads/master", master.getName()); A nice side-effect of this change is the storage type of the symbolic reference is no longer ambiguous with the storge type of the underlying reference it targets. In the above example, if master was only available in the packed-refs file, then the following is also true: assertSame(Ref.Storage.LOOSE, HEAD.getStorage()); assertSame(Ref.Storage.PACKED, master.getStorage()); (Prior to this change we returned the ambiguous storage of LOOSE_PACKED for HEAD, which was confusing since it wasn't actually true on disk). Another nice side-effect of this change is all intermediate symbolic references are preserved, and are therefore visible to the application when they walk the target chain. We can now correctly inspect chains of symbolic references. As a result of this change the Ref.getOrigName() method has been removed from the API. Applications should identify a symbolic reference by testing for isSymbolic() and not by using an arcane string comparsion between properties. Abstract the RefDatabase storage: --------------------------------- RefDatabase is now abstract, similar to ObjectDatabase, and a new concrete implementation called RefDirectory is used for the traditional on-disk storage layout. In the future we plan to support additional implementations, such as a pure in-memory RefDatabase for unit testing purposes. Optimize RefDirectory: ---------------------- The implementation of the in-memory reference cache, reading, and update routines has been completely rewritten. Much of the code was heavily borrowed or cribbed from the prior implementation, so copyright notices have been left intact as much as possible. The RefDirectory cache no longer confuses symbolic references with normal references. This permits the cache to resolve the value of a symbolic reference as late as possible, ensuring it is always current, without needing to maintain reverse pointers. The cache is now 2 sorted RefLists, rather than 3 HashMaps. Using sorted lists allows the implementation to reduce the in-memory footprint when storing many refs. Using specialized types for the elements allows the code to avoid additional map lookups for auxiliary stat information. To improve scan time during getRefs(), the lists are returned via a copy-on-write contract. Most callers of getRefs() do not modify the returned collections, so the copy-on-write semantics improves access on repositories with a large number of packed references. Iterator traversals of the returned Map<String,Ref> are performed using a simple merge-join of the two cache lists, ensuring we can perform the entire traversal in linear time as a function of the number of references: O(PackedRefs + LooseRefs). Scans of the loose reference space to update the cache run in O(LooseRefs log LooseRefs) time, as the directory contents are sorted before being merged against the in-memory cache. Since the majority of stable references are kept packed, there typically are only a handful of reference names to be sorted, so the sorting cost should not be very high. Locking is reduced during getRefs() by taking advantage of the copy-on-write semantics of the improved cache data structure. This permits concurrent readers to pull back references without blocking each other. If there is contention updating the cache during a scan, one or more updates are simply skipped and will get picked up again in a future scan. Writing to the $GIT_DIR/packed-refs during reference delete is now fully atomic. The file is locked, reparsed fresh, and written back out if a change is necessary. This avoids all race conditions with concurrent external updates of the packed-refs file. The RefLogWriter class has been fully folded into RefDirectory and is therefore deleted. Maintaining the reference's log is the responsiblity of the database implementation, and not all implementations will use java.io for access. Future work still remains to be done to abstract the ReflogReader class away from local disk IO. Change-Id: I26b9287c45a4b2d2be35ba2849daa316f5eec85d Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
Rewrite reference handling to be abstract and accurate This commit actually does three major changes to the way references are handled within JGit. Unfortunately they were easier to do as a single massive commit than to break them up into smaller units. Disambiguate symbolic references: --------------------------------- Reporting a symbolic reference such as HEAD as though it were any other normal reference like refs/heads/master causes subtle programming errors. We have been bitten by this error on several occasions, as have some downstream applications written by myself. Instead of reporting HEAD as a reference whose name differs from its "original name", report it as an actual SymbolicRef object that the application can test the type and examine the target of. With this change, Ref is now an abstract type with different subclasses for the different types. In the classical example of "HEAD" being a symbolic reference to branch "refs/heads/master", the Repository.getAllRefs() method will now return: Map<String, Ref> all = repository.getAllRefs(); SymbolicRef HEAD = (SymbolicRef) all.get("HEAD"); ObjectIdRef master = (ObjectIdRef) all.get("refs/heads/master"); assertSame(master, HEAD.getTarget()); assertSame(master.getObjectId(), HEAD.getObjectId()); assertEquals("HEAD", HEAD.getName()); assertEquals("refs/heads/master", master.getName()); A nice side-effect of this change is the storage type of the symbolic reference is no longer ambiguous with the storge type of the underlying reference it targets. In the above example, if master was only available in the packed-refs file, then the following is also true: assertSame(Ref.Storage.LOOSE, HEAD.getStorage()); assertSame(Ref.Storage.PACKED, master.getStorage()); (Prior to this change we returned the ambiguous storage of LOOSE_PACKED for HEAD, which was confusing since it wasn't actually true on disk). Another nice side-effect of this change is all intermediate symbolic references are preserved, and are therefore visible to the application when they walk the target chain. We can now correctly inspect chains of symbolic references. As a result of this change the Ref.getOrigName() method has been removed from the API. Applications should identify a symbolic reference by testing for isSymbolic() and not by using an arcane string comparsion between properties. Abstract the RefDatabase storage: --------------------------------- RefDatabase is now abstract, similar to ObjectDatabase, and a new concrete implementation called RefDirectory is used for the traditional on-disk storage layout. In the future we plan to support additional implementations, such as a pure in-memory RefDatabase for unit testing purposes. Optimize RefDirectory: ---------------------- The implementation of the in-memory reference cache, reading, and update routines has been completely rewritten. Much of the code was heavily borrowed or cribbed from the prior implementation, so copyright notices have been left intact as much as possible. The RefDirectory cache no longer confuses symbolic references with normal references. This permits the cache to resolve the value of a symbolic reference as late as possible, ensuring it is always current, without needing to maintain reverse pointers. The cache is now 2 sorted RefLists, rather than 3 HashMaps. Using sorted lists allows the implementation to reduce the in-memory footprint when storing many refs. Using specialized types for the elements allows the code to avoid additional map lookups for auxiliary stat information. To improve scan time during getRefs(), the lists are returned via a copy-on-write contract. Most callers of getRefs() do not modify the returned collections, so the copy-on-write semantics improves access on repositories with a large number of packed references. Iterator traversals of the returned Map<String,Ref> are performed using a simple merge-join of the two cache lists, ensuring we can perform the entire traversal in linear time as a function of the number of references: O(PackedRefs + LooseRefs). Scans of the loose reference space to update the cache run in O(LooseRefs log LooseRefs) time, as the directory contents are sorted before being merged against the in-memory cache. Since the majority of stable references are kept packed, there typically are only a handful of reference names to be sorted, so the sorting cost should not be very high. Locking is reduced during getRefs() by taking advantage of the copy-on-write semantics of the improved cache data structure. This permits concurrent readers to pull back references without blocking each other. If there is contention updating the cache during a scan, one or more updates are simply skipped and will get picked up again in a future scan. Writing to the $GIT_DIR/packed-refs during reference delete is now fully atomic. The file is locked, reparsed fresh, and written back out if a change is necessary. This avoids all race conditions with concurrent external updates of the packed-refs file. The RefLogWriter class has been fully folded into RefDirectory and is therefore deleted. Maintaining the reference's log is the responsiblity of the database implementation, and not all implementations will use java.io for access. Future work still remains to be done to abstract the ReflogReader class away from local disk IO. Change-Id: I26b9287c45a4b2d2be35ba2849daa316f5eec85d Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
Rewrite reference handling to be abstract and accurate This commit actually does three major changes to the way references are handled within JGit. Unfortunately they were easier to do as a single massive commit than to break them up into smaller units. Disambiguate symbolic references: --------------------------------- Reporting a symbolic reference such as HEAD as though it were any other normal reference like refs/heads/master causes subtle programming errors. We have been bitten by this error on several occasions, as have some downstream applications written by myself. Instead of reporting HEAD as a reference whose name differs from its "original name", report it as an actual SymbolicRef object that the application can test the type and examine the target of. With this change, Ref is now an abstract type with different subclasses for the different types. In the classical example of "HEAD" being a symbolic reference to branch "refs/heads/master", the Repository.getAllRefs() method will now return: Map<String, Ref> all = repository.getAllRefs(); SymbolicRef HEAD = (SymbolicRef) all.get("HEAD"); ObjectIdRef master = (ObjectIdRef) all.get("refs/heads/master"); assertSame(master, HEAD.getTarget()); assertSame(master.getObjectId(), HEAD.getObjectId()); assertEquals("HEAD", HEAD.getName()); assertEquals("refs/heads/master", master.getName()); A nice side-effect of this change is the storage type of the symbolic reference is no longer ambiguous with the storge type of the underlying reference it targets. In the above example, if master was only available in the packed-refs file, then the following is also true: assertSame(Ref.Storage.LOOSE, HEAD.getStorage()); assertSame(Ref.Storage.PACKED, master.getStorage()); (Prior to this change we returned the ambiguous storage of LOOSE_PACKED for HEAD, which was confusing since it wasn't actually true on disk). Another nice side-effect of this change is all intermediate symbolic references are preserved, and are therefore visible to the application when they walk the target chain. We can now correctly inspect chains of symbolic references. As a result of this change the Ref.getOrigName() method has been removed from the API. Applications should identify a symbolic reference by testing for isSymbolic() and not by using an arcane string comparsion between properties. Abstract the RefDatabase storage: --------------------------------- RefDatabase is now abstract, similar to ObjectDatabase, and a new concrete implementation called RefDirectory is used for the traditional on-disk storage layout. In the future we plan to support additional implementations, such as a pure in-memory RefDatabase for unit testing purposes. Optimize RefDirectory: ---------------------- The implementation of the in-memory reference cache, reading, and update routines has been completely rewritten. Much of the code was heavily borrowed or cribbed from the prior implementation, so copyright notices have been left intact as much as possible. The RefDirectory cache no longer confuses symbolic references with normal references. This permits the cache to resolve the value of a symbolic reference as late as possible, ensuring it is always current, without needing to maintain reverse pointers. The cache is now 2 sorted RefLists, rather than 3 HashMaps. Using sorted lists allows the implementation to reduce the in-memory footprint when storing many refs. Using specialized types for the elements allows the code to avoid additional map lookups for auxiliary stat information. To improve scan time during getRefs(), the lists are returned via a copy-on-write contract. Most callers of getRefs() do not modify the returned collections, so the copy-on-write semantics improves access on repositories with a large number of packed references. Iterator traversals of the returned Map<String,Ref> are performed using a simple merge-join of the two cache lists, ensuring we can perform the entire traversal in linear time as a function of the number of references: O(PackedRefs + LooseRefs). Scans of the loose reference space to update the cache run in O(LooseRefs log LooseRefs) time, as the directory contents are sorted before being merged against the in-memory cache. Since the majority of stable references are kept packed, there typically are only a handful of reference names to be sorted, so the sorting cost should not be very high. Locking is reduced during getRefs() by taking advantage of the copy-on-write semantics of the improved cache data structure. This permits concurrent readers to pull back references without blocking each other. If there is contention updating the cache during a scan, one or more updates are simply skipped and will get picked up again in a future scan. Writing to the $GIT_DIR/packed-refs during reference delete is now fully atomic. The file is locked, reparsed fresh, and written back out if a change is necessary. This avoids all race conditions with concurrent external updates of the packed-refs file. The RefLogWriter class has been fully folded into RefDirectory and is therefore deleted. Maintaining the reference's log is the responsiblity of the database implementation, and not all implementations will use java.io for access. Future work still remains to be done to abstract the ReflogReader class away from local disk IO. Change-Id: I26b9287c45a4b2d2be35ba2849daa316f5eec85d Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030
  1. /*
  2. * Copyright (C) 2008, Charles O'Farrell <charleso@charleso.org>
  3. * Copyright (C) 2009-2010, Google Inc.
  4. * Copyright (C) 2008-2013, Robin Rosenberg <robin.rosenberg@dewire.com>
  5. * and other copyright owners as documented in the project's IP log.
  6. *
  7. * This program and the accompanying materials are made available
  8. * under the terms of the Eclipse Distribution License v1.0 which
  9. * accompanies this distribution, is reproduced below, and is
  10. * available at http://www.eclipse.org/org/documents/edl-v10.php
  11. *
  12. * All rights reserved.
  13. *
  14. * Redistribution and use in source and binary forms, with or
  15. * without modification, are permitted provided that the following
  16. * conditions are met:
  17. *
  18. * - Redistributions of source code must retain the above copyright
  19. * notice, this list of conditions and the following disclaimer.
  20. *
  21. * - Redistributions in binary form must reproduce the above
  22. * copyright notice, this list of conditions and the following
  23. * disclaimer in the documentation and/or other materials provided
  24. * with the distribution.
  25. *
  26. * - Neither the name of the Eclipse Foundation, Inc. nor the
  27. * names of its contributors may be used to endorse or promote
  28. * products derived from this software without specific prior
  29. * written permission.
  30. *
  31. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  32. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  33. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  34. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  35. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  36. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  37. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  38. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  39. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  40. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  41. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  42. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  43. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  44. */
  45. package org.eclipse.jgit.internal.storage.file;
  46. import static org.eclipse.jgit.junit.Assert.assertEquals;
  47. import static org.eclipse.jgit.lib.Constants.CHARSET;
  48. import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX;
  49. import static org.junit.Assert.assertEquals;
  50. import static org.junit.Assert.assertFalse;
  51. import static org.junit.Assert.assertNotNull;
  52. import static org.junit.Assert.assertNotSame;
  53. import static org.junit.Assert.assertNull;
  54. import static org.junit.Assert.assertSame;
  55. import static org.junit.Assert.assertTrue;
  56. import static org.junit.Assert.fail;
  57. import java.io.File;
  58. import java.io.IOException;
  59. import java.util.List;
  60. import java.util.Map;
  61. import java.util.Map.Entry;
  62. import org.eclipse.jgit.lib.AnyObjectId;
  63. import org.eclipse.jgit.lib.Constants;
  64. import org.eclipse.jgit.lib.ObjectId;
  65. import org.eclipse.jgit.lib.ObjectInserter;
  66. import org.eclipse.jgit.lib.PersonIdent;
  67. import org.eclipse.jgit.lib.Ref;
  68. import org.eclipse.jgit.lib.RefRename;
  69. import org.eclipse.jgit.lib.RefUpdate;
  70. import org.eclipse.jgit.lib.RefUpdate.Result;
  71. import org.eclipse.jgit.lib.ReflogEntry;
  72. import org.eclipse.jgit.lib.ReflogReader;
  73. import org.eclipse.jgit.lib.Repository;
  74. import org.eclipse.jgit.revwalk.RevCommit;
  75. import org.eclipse.jgit.revwalk.RevWalk;
  76. import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
  77. import org.junit.Test;
  78. public class RefUpdateTest extends SampleDataRepositoryTestCase {
  79. private void writeSymref(String src, String dst) throws IOException {
  80. RefUpdate u = db.updateRef(src);
  81. switch (u.link(dst)) {
  82. case NEW:
  83. case FORCED:
  84. case NO_CHANGE:
  85. break;
  86. default:
  87. fail("link " + src + " to " + dst);
  88. }
  89. }
  90. private RefUpdate updateRef(String name) throws IOException {
  91. final RefUpdate ref = db.updateRef(name);
  92. ref.setNewObjectId(db.resolve(Constants.HEAD));
  93. return ref;
  94. }
  95. private void delete(RefUpdate ref, Result expected)
  96. throws IOException {
  97. delete(ref, expected, true, true);
  98. }
  99. private void delete(final RefUpdate ref, final Result expected,
  100. final boolean exists, final boolean removed) throws IOException {
  101. delete(db, ref, expected, exists, removed);
  102. }
  103. private void delete(Repository repo, final RefUpdate ref, final Result expected,
  104. final boolean exists, final boolean removed) throws IOException {
  105. assertEquals(exists, repo.getAllRefs().containsKey(ref.getName()));
  106. assertEquals(expected, ref.delete());
  107. assertEquals(!removed, repo.getAllRefs().containsKey(ref.getName()));
  108. }
  109. @Test
  110. public void testNoCacheObjectIdSubclass() throws IOException {
  111. final String newRef = "refs/heads/abc";
  112. final RefUpdate ru = updateRef(newRef);
  113. final SubclassedId newid = new SubclassedId(ru.getNewObjectId());
  114. ru.setNewObjectId(newid);
  115. Result update = ru.update();
  116. assertEquals(Result.NEW, update);
  117. final Ref r = db.getAllRefs().get(newRef);
  118. assertNotNull(r);
  119. assertEquals(newRef, r.getName());
  120. assertNotNull(r.getObjectId());
  121. assertNotSame(newid, r.getObjectId());
  122. assertSame(ObjectId.class, r.getObjectId().getClass());
  123. assertEquals(newid, r.getObjectId());
  124. List<ReflogEntry> reverseEntries1 = db
  125. .getReflogReader("refs/heads/abc").getReverseEntries();
  126. ReflogEntry entry1 = reverseEntries1.get(0);
  127. assertEquals(1, reverseEntries1.size());
  128. assertEquals(ObjectId.zeroId(), entry1.getOldId());
  129. assertEquals(r.getObjectId(), entry1.getNewId());
  130. assertEquals(new PersonIdent(db).toString(), entry1.getWho().toString());
  131. assertEquals("", entry1.getComment());
  132. List<ReflogEntry> reverseEntries2 = db.getReflogReader("HEAD")
  133. .getReverseEntries();
  134. assertEquals(0, reverseEntries2.size());
  135. }
  136. @Test
  137. public void testNewNamespaceConflictWithLoosePrefixNameExists()
  138. throws IOException {
  139. final String newRef = "refs/heads/z";
  140. final RefUpdate ru = updateRef(newRef);
  141. Result update = ru.update();
  142. assertEquals(Result.NEW, update);
  143. // end setup
  144. final String newRef2 = "refs/heads/z/a";
  145. final RefUpdate ru2 = updateRef(newRef2);
  146. Result update2 = ru2.update();
  147. assertEquals(Result.LOCK_FAILURE, update2);
  148. assertEquals(1, db.getReflogReader("refs/heads/z").getReverseEntries().size());
  149. assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
  150. }
  151. @Test
  152. public void testNewNamespaceConflictWithPackedPrefixNameExists()
  153. throws IOException {
  154. final String newRef = "refs/heads/master/x";
  155. final RefUpdate ru = updateRef(newRef);
  156. Result update = ru.update();
  157. assertEquals(Result.LOCK_FAILURE, update);
  158. assertNull(db.getReflogReader("refs/heads/master/x"));
  159. assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
  160. }
  161. @Test
  162. public void testNewNamespaceConflictWithLoosePrefixOfExisting()
  163. throws IOException {
  164. final String newRef = "refs/heads/z/a";
  165. final RefUpdate ru = updateRef(newRef);
  166. Result update = ru.update();
  167. assertEquals(Result.NEW, update);
  168. // end setup
  169. final String newRef2 = "refs/heads/z";
  170. final RefUpdate ru2 = updateRef(newRef2);
  171. Result update2 = ru2.update();
  172. assertEquals(Result.LOCK_FAILURE, update2);
  173. assertEquals(1, db.getReflogReader("refs/heads/z/a").getReverseEntries().size());
  174. assertNull(db.getReflogReader("refs/heads/z"));
  175. assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
  176. }
  177. @Test
  178. public void testNewNamespaceConflictWithPackedPrefixOfExisting()
  179. throws IOException {
  180. final String newRef = "refs/heads/prefix";
  181. final RefUpdate ru = updateRef(newRef);
  182. Result update = ru.update();
  183. assertEquals(Result.LOCK_FAILURE, update);
  184. assertNull(db.getReflogReader("refs/heads/prefix"));
  185. assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
  186. }
  187. /**
  188. * Delete a ref that is pointed to by HEAD
  189. *
  190. * @throws IOException
  191. */
  192. @Test
  193. public void testDeleteHEADreferencedRef() throws IOException {
  194. ObjectId pid = db.resolve("refs/heads/master^");
  195. RefUpdate updateRef = db.updateRef("refs/heads/master");
  196. updateRef.setNewObjectId(pid);
  197. updateRef.setForceUpdate(true);
  198. Result update = updateRef.update();
  199. assertEquals(Result.FORCED, update); // internal
  200. RefUpdate updateRef2 = db.updateRef("refs/heads/master");
  201. Result delete = updateRef2.delete();
  202. assertEquals(Result.REJECTED_CURRENT_BRANCH, delete);
  203. assertEquals(pid, db.resolve("refs/heads/master"));
  204. assertEquals(1,db.getReflogReader("refs/heads/master").getReverseEntries().size());
  205. assertEquals(0,db.getReflogReader("HEAD").getReverseEntries().size());
  206. }
  207. @Test
  208. public void testLooseDelete() throws IOException {
  209. final String newRef = "refs/heads/abc";
  210. RefUpdate ref = updateRef(newRef);
  211. ref.update(); // create loose ref
  212. ref = updateRef(newRef); // refresh
  213. delete(ref, Result.NO_CHANGE);
  214. assertNull(db.getReflogReader("refs/heads/abc"));
  215. }
  216. @Test
  217. public void testDeleteHead() throws IOException {
  218. final RefUpdate ref = updateRef(Constants.HEAD);
  219. delete(ref, Result.REJECTED_CURRENT_BRANCH, true, false);
  220. assertEquals(0, db.getReflogReader("refs/heads/master").getReverseEntries().size());
  221. assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
  222. }
  223. @Test
  224. public void testDeleteHeadInBareRepo() throws IOException {
  225. Repository bareRepo = createBareRepository();
  226. String master = "refs/heads/master";
  227. Ref head = bareRepo.exactRef(Constants.HEAD);
  228. assertNotNull(head);
  229. assertTrue(head.isSymbolic());
  230. assertEquals(master, head.getLeaf().getName());
  231. assertNull(head.getObjectId());
  232. assertNull(bareRepo.exactRef(master));
  233. ObjectId blobId;
  234. try (ObjectInserter ins = bareRepo.newObjectInserter()) {
  235. blobId = ins.insert(Constants.OBJ_BLOB, "contents".getBytes(CHARSET));
  236. ins.flush();
  237. }
  238. // Create master via HEAD, so we delete it.
  239. RefUpdate ref = bareRepo.updateRef(Constants.HEAD);
  240. ref.setNewObjectId(blobId);
  241. assertEquals(Result.NEW, ref.update());
  242. head = bareRepo.exactRef(Constants.HEAD);
  243. assertTrue(head.isSymbolic());
  244. assertEquals(master, head.getLeaf().getName());
  245. assertEquals(blobId, head.getLeaf().getObjectId());
  246. assertEquals(blobId, bareRepo.exactRef(master).getObjectId());
  247. // Unlike in a non-bare repo, deleting the HEAD is allowed, and leaves HEAD
  248. // back in a dangling state.
  249. ref = bareRepo.updateRef(Constants.HEAD);
  250. ref.setExpectedOldObjectId(blobId);
  251. ref.setForceUpdate(true);
  252. delete(bareRepo, ref, Result.FORCED, true, true);
  253. head = bareRepo.exactRef(Constants.HEAD);
  254. assertNotNull(head);
  255. assertTrue(head.isSymbolic());
  256. assertEquals(master, head.getLeaf().getName());
  257. assertNull(head.getObjectId());
  258. assertNull(bareRepo.exactRef(master));
  259. }
  260. @Test
  261. public void testDeleteSymref() throws IOException {
  262. RefUpdate dst = updateRef("refs/heads/abc");
  263. assertEquals(Result.NEW, dst.update());
  264. ObjectId id = dst.getNewObjectId();
  265. RefUpdate u = db.updateRef("refs/symref");
  266. assertEquals(Result.NEW, u.link(dst.getName()));
  267. Ref ref = db.exactRef(u.getName());
  268. assertNotNull(ref);
  269. assertTrue(ref.isSymbolic());
  270. assertEquals(dst.getName(), ref.getLeaf().getName());
  271. assertEquals(id, ref.getLeaf().getObjectId());
  272. u = db.updateRef(u.getName());
  273. u.setDetachingSymbolicRef();
  274. u.setForceUpdate(true);
  275. assertEquals(Result.FORCED, u.delete());
  276. assertNull(db.exactRef(u.getName()));
  277. ref = db.exactRef(dst.getName());
  278. assertNotNull(ref);
  279. assertFalse(ref.isSymbolic());
  280. assertEquals(id, ref.getObjectId());
  281. }
  282. /**
  283. * Delete a loose ref and make sure the directory in refs is deleted too,
  284. * and the reflog dir too
  285. *
  286. * @throws IOException
  287. */
  288. @Test
  289. public void testDeleteLooseAndItsDirectory() throws IOException {
  290. ObjectId pid = db.resolve("refs/heads/c^");
  291. RefUpdate updateRef = db.updateRef("refs/heads/z/c");
  292. updateRef.setNewObjectId(pid);
  293. updateRef.setForceUpdate(true);
  294. updateRef.setRefLogMessage("new test ref", false);
  295. Result update = updateRef.update();
  296. assertEquals(Result.NEW, update); // internal
  297. assertTrue(new File(db.getDirectory(), Constants.R_HEADS + "z")
  298. .exists());
  299. assertTrue(new File(db.getDirectory(), "logs/refs/heads/z").exists());
  300. // The real test here
  301. RefUpdate updateRef2 = db.updateRef("refs/heads/z/c");
  302. updateRef2.setForceUpdate(true);
  303. Result delete = updateRef2.delete();
  304. assertEquals(Result.FORCED, delete);
  305. assertNull(db.resolve("refs/heads/z/c"));
  306. assertFalse(new File(db.getDirectory(), Constants.R_HEADS + "z")
  307. .exists());
  308. assertFalse(new File(db.getDirectory(), "logs/refs/heads/z").exists());
  309. }
  310. @Test
  311. public void testDeleteNotFound() throws IOException {
  312. final RefUpdate ref = updateRef("refs/heads/xyz");
  313. delete(ref, Result.NEW, false, true);
  314. }
  315. @Test
  316. public void testDeleteFastForward() throws IOException {
  317. final RefUpdate ref = updateRef("refs/heads/a");
  318. delete(ref, Result.FAST_FORWARD);
  319. }
  320. @Test
  321. public void testDeleteForce() throws IOException {
  322. final RefUpdate ref = db.updateRef("refs/heads/b");
  323. ref.setNewObjectId(db.resolve("refs/heads/a"));
  324. delete(ref, Result.REJECTED, true, false);
  325. ref.setForceUpdate(true);
  326. delete(ref, Result.FORCED);
  327. }
  328. @Test
  329. public void testDeleteWithoutHead() throws IOException {
  330. // Prepare repository without HEAD
  331. RefUpdate refUpdate = db.updateRef(Constants.HEAD, true);
  332. refUpdate.setForceUpdate(true);
  333. refUpdate.setNewObjectId(ObjectId.zeroId());
  334. Result updateResult = refUpdate.update();
  335. assertEquals(Result.FORCED, updateResult);
  336. Result deleteHeadResult = db.updateRef(Constants.HEAD).delete();
  337. assertEquals(Result.NO_CHANGE, deleteHeadResult);
  338. // Any result is ok as long as it's not an NPE
  339. db.updateRef(Constants.R_HEADS + "master").delete();
  340. }
  341. @Test
  342. public void testRefKeySameAsName() {
  343. Map<String, Ref> allRefs = db.getAllRefs();
  344. for (Entry<String, Ref> e : allRefs.entrySet()) {
  345. assertEquals(e.getKey(), e.getValue().getName());
  346. }
  347. }
  348. /**
  349. * Try modify a ref forward, fast forward
  350. *
  351. * @throws IOException
  352. */
  353. @Test
  354. public void testUpdateRefForward() throws IOException {
  355. ObjectId ppid = db.resolve("refs/heads/master^");
  356. ObjectId pid = db.resolve("refs/heads/master");
  357. RefUpdate updateRef = db.updateRef("refs/heads/master");
  358. updateRef.setNewObjectId(ppid);
  359. updateRef.setForceUpdate(true);
  360. Result update = updateRef.update();
  361. assertEquals(Result.FORCED, update);
  362. assertEquals(ppid, db.resolve("refs/heads/master"));
  363. // real test
  364. RefUpdate updateRef2 = db.updateRef("refs/heads/master");
  365. updateRef2.setNewObjectId(pid);
  366. Result update2 = updateRef2.update();
  367. assertEquals(Result.FAST_FORWARD, update2);
  368. assertEquals(pid, db.resolve("refs/heads/master"));
  369. }
  370. /**
  371. * Update the HEAD ref. Only it should be changed, not what it points to.
  372. *
  373. * @throws Exception
  374. */
  375. @Test
  376. public void testUpdateRefDetached() throws Exception {
  377. ObjectId pid = db.resolve("refs/heads/master");
  378. ObjectId ppid = db.resolve("refs/heads/master^");
  379. RefUpdate updateRef = db.updateRef("HEAD", true);
  380. updateRef.setForceUpdate(true);
  381. updateRef.setNewObjectId(ppid);
  382. Result update = updateRef.update();
  383. assertEquals(Result.FORCED, update);
  384. assertEquals(ppid, db.resolve("HEAD"));
  385. Ref ref = db.exactRef("HEAD");
  386. assertEquals("HEAD", ref.getName());
  387. assertTrue("is detached", !ref.isSymbolic());
  388. // the branch HEAD referred to is left untouched
  389. assertEquals(pid, db.resolve("refs/heads/master"));
  390. ReflogReader reflogReader = db.getReflogReader("HEAD");
  391. ReflogEntry e = reflogReader.getReverseEntries().get(0);
  392. assertEquals(pid, e.getOldId());
  393. assertEquals(ppid, e.getNewId());
  394. assertEquals("GIT_COMMITTER_EMAIL", e.getWho().getEmailAddress());
  395. assertEquals("GIT_COMMITTER_NAME", e.getWho().getName());
  396. assertEquals(1250379778000L, e.getWho().getWhen().getTime());
  397. }
  398. /**
  399. * Update the HEAD ref when the referenced branch is unborn
  400. *
  401. * @throws Exception
  402. */
  403. @Test
  404. public void testUpdateRefDetachedUnbornHead() throws Exception {
  405. ObjectId ppid = db.resolve("refs/heads/master^");
  406. writeSymref("HEAD", "refs/heads/unborn");
  407. RefUpdate updateRef = db.updateRef("HEAD", true);
  408. updateRef.setForceUpdate(true);
  409. updateRef.setNewObjectId(ppid);
  410. Result update = updateRef.update();
  411. assertEquals(Result.NEW, update);
  412. assertEquals(ppid, db.resolve("HEAD"));
  413. Ref ref = db.exactRef("HEAD");
  414. assertEquals("HEAD", ref.getName());
  415. assertTrue("is detached", !ref.isSymbolic());
  416. // the branch HEAD referred to is left untouched
  417. assertNull(db.resolve("refs/heads/unborn"));
  418. ReflogReader reflogReader = db.getReflogReader("HEAD");
  419. ReflogEntry e = reflogReader.getReverseEntries().get(0);
  420. assertEquals(ObjectId.zeroId(), e.getOldId());
  421. assertEquals(ppid, e.getNewId());
  422. assertEquals("GIT_COMMITTER_EMAIL", e.getWho().getEmailAddress());
  423. assertEquals("GIT_COMMITTER_NAME", e.getWho().getName());
  424. assertEquals(1250379778000L, e.getWho().getWhen().getTime());
  425. }
  426. /**
  427. * Delete a ref that exists both as packed and loose. Make sure the ref
  428. * cannot be resolved after delete.
  429. *
  430. * @throws IOException
  431. */
  432. @Test
  433. public void testDeleteLoosePacked() throws IOException {
  434. ObjectId pid = db.resolve("refs/heads/c^");
  435. RefUpdate updateRef = db.updateRef("refs/heads/c");
  436. updateRef.setNewObjectId(pid);
  437. updateRef.setForceUpdate(true);
  438. Result update = updateRef.update();
  439. assertEquals(Result.FORCED, update); // internal
  440. // The real test here
  441. RefUpdate updateRef2 = db.updateRef("refs/heads/c");
  442. updateRef2.setForceUpdate(true);
  443. Result delete = updateRef2.delete();
  444. assertEquals(Result.FORCED, delete);
  445. assertNull(db.resolve("refs/heads/c"));
  446. }
  447. /**
  448. * Try modify a ref to same
  449. *
  450. * @throws IOException
  451. */
  452. @Test
  453. public void testUpdateRefNoChange() throws IOException {
  454. ObjectId pid = db.resolve("refs/heads/master");
  455. RefUpdate updateRef = db.updateRef("refs/heads/master");
  456. updateRef.setNewObjectId(pid);
  457. Result update = updateRef.update();
  458. assertEquals(Result.NO_CHANGE, update);
  459. assertEquals(pid, db.resolve("refs/heads/master"));
  460. }
  461. /**
  462. * Test case originating from
  463. * <a href="http://bugs.eclipse.org/285991">bug 285991</a>
  464. *
  465. * Make sure the in memory cache is updated properly after
  466. * update of symref. This one did not fail because the
  467. * ref was packed due to implementation issues.
  468. *
  469. * @throws Exception
  470. */
  471. @Test
  472. public void testRefsCacheAfterUpdate() throws Exception {
  473. // Do not use the defalt repo for this case.
  474. Map<String, Ref> allRefs = db.getAllRefs();
  475. ObjectId oldValue = db.resolve("HEAD");
  476. ObjectId newValue = db.resolve("HEAD^");
  477. // first make HEAD refer to loose ref
  478. RefUpdate updateRef = db.updateRef(Constants.HEAD);
  479. updateRef.setForceUpdate(true);
  480. updateRef.setNewObjectId(newValue);
  481. Result update = updateRef.update();
  482. assertEquals(Result.FORCED, update);
  483. // now update that ref
  484. updateRef = db.updateRef(Constants.HEAD);
  485. updateRef.setNewObjectId(oldValue);
  486. update = updateRef.update();
  487. assertEquals(Result.FAST_FORWARD, update);
  488. allRefs = db.getAllRefs();
  489. Ref master = allRefs.get("refs/heads/master");
  490. Ref head = allRefs.get("HEAD");
  491. assertEquals("refs/heads/master", master.getName());
  492. assertEquals("HEAD", head.getName());
  493. assertTrue("is symbolic reference", head.isSymbolic());
  494. assertSame(master, head.getTarget());
  495. }
  496. /**
  497. * Test case originating from
  498. * <a href="http://bugs.eclipse.org/285991">bug 285991</a>
  499. *
  500. * Make sure the in memory cache is updated properly after
  501. * update of symref.
  502. *
  503. * @throws Exception
  504. */
  505. @Test
  506. public void testRefsCacheAfterUpdateLooseOnly() throws Exception {
  507. // Do not use the defalt repo for this case.
  508. Map<String, Ref> allRefs = db.getAllRefs();
  509. ObjectId oldValue = db.resolve("HEAD");
  510. writeSymref(Constants.HEAD, "refs/heads/newref");
  511. RefUpdate updateRef = db.updateRef(Constants.HEAD);
  512. updateRef.setForceUpdate(true);
  513. updateRef.setNewObjectId(oldValue);
  514. Result update = updateRef.update();
  515. assertEquals(Result.NEW, update);
  516. allRefs = db.getAllRefs();
  517. Ref head = allRefs.get("HEAD");
  518. Ref newref = allRefs.get("refs/heads/newref");
  519. assertEquals("refs/heads/newref", newref.getName());
  520. assertEquals("HEAD", head.getName());
  521. assertTrue("is symbolic reference", head.isSymbolic());
  522. assertSame(newref, head.getTarget());
  523. }
  524. /**
  525. * Try modify a ref, but get wrong expected old value
  526. *
  527. * @throws IOException
  528. */
  529. @Test
  530. public void testUpdateRefLockFailureWrongOldValue() throws IOException {
  531. ObjectId pid = db.resolve("refs/heads/master");
  532. RefUpdate updateRef = db.updateRef("refs/heads/master");
  533. updateRef.setNewObjectId(pid);
  534. updateRef.setExpectedOldObjectId(db.resolve("refs/heads/master^"));
  535. Result update = updateRef.update();
  536. assertEquals(Result.LOCK_FAILURE, update);
  537. assertEquals(pid, db.resolve("refs/heads/master"));
  538. }
  539. /**
  540. * Try modify a ref forward, fast forward, checking old value first
  541. *
  542. * @throws IOException
  543. */
  544. @Test
  545. public void testUpdateRefForwardWithCheck1() throws IOException {
  546. ObjectId ppid = db.resolve("refs/heads/master^");
  547. ObjectId pid = db.resolve("refs/heads/master");
  548. RefUpdate updateRef = db.updateRef("refs/heads/master");
  549. updateRef.setNewObjectId(ppid);
  550. updateRef.setForceUpdate(true);
  551. Result update = updateRef.update();
  552. assertEquals(Result.FORCED, update);
  553. assertEquals(ppid, db.resolve("refs/heads/master"));
  554. // real test
  555. RefUpdate updateRef2 = db.updateRef("refs/heads/master");
  556. updateRef2.setExpectedOldObjectId(ppid);
  557. updateRef2.setNewObjectId(pid);
  558. Result update2 = updateRef2.update();
  559. assertEquals(Result.FAST_FORWARD, update2);
  560. assertEquals(pid, db.resolve("refs/heads/master"));
  561. }
  562. /**
  563. * Try modify a ref forward, fast forward, checking old commit first
  564. *
  565. * @throws IOException
  566. */
  567. @Test
  568. public void testUpdateRefForwardWithCheck2() throws IOException {
  569. ObjectId ppid = db.resolve("refs/heads/master^");
  570. ObjectId pid = db.resolve("refs/heads/master");
  571. RefUpdate updateRef = db.updateRef("refs/heads/master");
  572. updateRef.setNewObjectId(ppid);
  573. updateRef.setForceUpdate(true);
  574. Result update = updateRef.update();
  575. assertEquals(Result.FORCED, update);
  576. assertEquals(ppid, db.resolve("refs/heads/master"));
  577. // real test
  578. try (RevWalk rw = new RevWalk(db)) {
  579. RevCommit old = rw.parseCommit(ppid);
  580. RefUpdate updateRef2 = db.updateRef("refs/heads/master");
  581. updateRef2.setExpectedOldObjectId(old);
  582. updateRef2.setNewObjectId(pid);
  583. Result update2 = updateRef2.update();
  584. assertEquals(Result.FAST_FORWARD, update2);
  585. assertEquals(pid, db.resolve("refs/heads/master"));
  586. }
  587. }
  588. /**
  589. * Try modify a ref that is locked
  590. *
  591. * @throws IOException
  592. */
  593. @Test
  594. public void testUpdateRefLockFailureLocked() throws IOException {
  595. ObjectId opid = db.resolve("refs/heads/master");
  596. ObjectId pid = db.resolve("refs/heads/master^");
  597. RefUpdate updateRef = db.updateRef("refs/heads/master");
  598. updateRef.setNewObjectId(pid);
  599. LockFile lockFile1 = new LockFile(new File(db.getDirectory(),
  600. "refs/heads/master"));
  601. try {
  602. assertTrue(lockFile1.lock()); // precondition to test
  603. Result update = updateRef.update();
  604. assertEquals(Result.LOCK_FAILURE, update);
  605. assertEquals(opid, db.resolve("refs/heads/master"));
  606. LockFile lockFile2 = new LockFile(new File(db.getDirectory(),"refs/heads/master"));
  607. assertFalse(lockFile2.lock()); // was locked, still is
  608. } finally {
  609. lockFile1.unlock();
  610. }
  611. }
  612. /**
  613. * Try to delete a ref. Delete requires force.
  614. *
  615. * @throws IOException
  616. */
  617. @Test
  618. public void testDeleteLoosePackedRejected() throws IOException {
  619. ObjectId pid = db.resolve("refs/heads/c^");
  620. ObjectId oldpid = db.resolve("refs/heads/c");
  621. RefUpdate updateRef = db.updateRef("refs/heads/c");
  622. updateRef.setNewObjectId(pid);
  623. Result update = updateRef.update();
  624. assertEquals(Result.REJECTED, update);
  625. assertEquals(oldpid, db.resolve("refs/heads/c"));
  626. }
  627. @Test
  628. public void testRenameBranchNoPreviousLog() throws IOException {
  629. assertFalse("precondition, no log on old branchg", new File(db
  630. .getDirectory(), "logs/refs/heads/b").exists());
  631. ObjectId rb = db.resolve("refs/heads/b");
  632. ObjectId oldHead = db.resolve(Constants.HEAD);
  633. assertFalse(rb.equals(oldHead)); // assumption for this test
  634. RefRename renameRef = db.renameRef("refs/heads/b",
  635. "refs/heads/new/name");
  636. Result result = renameRef.rename();
  637. assertEquals(Result.RENAMED, result);
  638. assertEquals(rb, db.resolve("refs/heads/new/name"));
  639. assertNull(db.resolve("refs/heads/b"));
  640. assertEquals(1, db.getReflogReader("new/name").getReverseEntries().size());
  641. assertEquals("Branch: renamed b to new/name", db.getReflogReader("new/name")
  642. .getLastEntry().getComment());
  643. assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists());
  644. assertEquals(oldHead, db.resolve(Constants.HEAD)); // unchanged
  645. }
  646. @Test
  647. public void testRenameBranchHasPreviousLog() throws IOException {
  648. ObjectId rb = db.resolve("refs/heads/b");
  649. ObjectId oldHead = db.resolve(Constants.HEAD);
  650. assertFalse("precondition for this test, branch b != HEAD", rb
  651. .equals(oldHead));
  652. writeReflog(db, rb, "Just a message", "refs/heads/b");
  653. assertTrue("log on old branch", new File(db.getDirectory(),
  654. "logs/refs/heads/b").exists());
  655. RefRename renameRef = db.renameRef("refs/heads/b",
  656. "refs/heads/new/name");
  657. Result result = renameRef.rename();
  658. assertEquals(Result.RENAMED, result);
  659. assertEquals(rb, db.resolve("refs/heads/new/name"));
  660. assertNull(db.resolve("refs/heads/b"));
  661. assertEquals(2, db.getReflogReader("new/name").getReverseEntries().size());
  662. assertEquals("Branch: renamed b to new/name", db.getReflogReader("new/name")
  663. .getLastEntry().getComment());
  664. assertEquals("Just a message", db.getReflogReader("new/name")
  665. .getReverseEntries().get(1).getComment());
  666. assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists());
  667. assertEquals(oldHead, db.resolve(Constants.HEAD)); // unchanged
  668. }
  669. @Test
  670. public void testRenameCurrentBranch() throws IOException {
  671. ObjectId rb = db.resolve("refs/heads/b");
  672. writeSymref(Constants.HEAD, "refs/heads/b");
  673. ObjectId oldHead = db.resolve(Constants.HEAD);
  674. assertEquals("internal test condition, b == HEAD", oldHead, rb);
  675. writeReflog(db, rb, "Just a message", "refs/heads/b");
  676. assertTrue("log on old branch", new File(db.getDirectory(),
  677. "logs/refs/heads/b").exists());
  678. RefRename renameRef = db.renameRef("refs/heads/b",
  679. "refs/heads/new/name");
  680. Result result = renameRef.rename();
  681. assertEquals(Result.RENAMED, result);
  682. assertEquals(rb, db.resolve("refs/heads/new/name"));
  683. assertNull(db.resolve("refs/heads/b"));
  684. assertEquals("Branch: renamed b to new/name", db.getReflogReader(
  685. "new/name").getLastEntry().getComment());
  686. assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists());
  687. assertEquals(rb, db.resolve(Constants.HEAD));
  688. assertEquals(2, db.getReflogReader("new/name").getReverseEntries().size());
  689. assertEquals("Branch: renamed b to new/name", db.getReflogReader("new/name").getReverseEntries().get(0).getComment());
  690. assertEquals("Just a message", db.getReflogReader("new/name").getReverseEntries().get(1).getComment());
  691. }
  692. @Test
  693. public void testRenameBranchAlsoInPack() throws IOException {
  694. ObjectId rb = db.resolve("refs/heads/b");
  695. ObjectId rb2 = db.resolve("refs/heads/b~1");
  696. assertEquals(Ref.Storage.PACKED, db.exactRef("refs/heads/b").getStorage());
  697. RefUpdate updateRef = db.updateRef("refs/heads/b");
  698. updateRef.setNewObjectId(rb2);
  699. updateRef.setForceUpdate(true);
  700. Result update = updateRef.update();
  701. assertEquals("internal check new ref is loose", Result.FORCED, update);
  702. assertEquals(Ref.Storage.LOOSE, db.exactRef("refs/heads/b").getStorage());
  703. writeReflog(db, rb, "Just a message", "refs/heads/b");
  704. assertTrue("log on old branch", new File(db.getDirectory(),
  705. "logs/refs/heads/b").exists());
  706. RefRename renameRef = db.renameRef("refs/heads/b",
  707. "refs/heads/new/name");
  708. Result result = renameRef.rename();
  709. assertEquals(Result.RENAMED, result);
  710. assertEquals(rb2, db.resolve("refs/heads/new/name"));
  711. assertNull(db.resolve("refs/heads/b"));
  712. assertEquals("Branch: renamed b to new/name", db.getReflogReader(
  713. "new/name").getLastEntry().getComment());
  714. assertEquals(3, db.getReflogReader("refs/heads/new/name").getReverseEntries().size());
  715. assertEquals("Branch: renamed b to new/name", db.getReflogReader("refs/heads/new/name").getReverseEntries().get(0).getComment());
  716. assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
  717. // make sure b's log file is gone too.
  718. assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists());
  719. // Create new Repository instance, to reread caches and make sure our
  720. // assumptions are persistent.
  721. try (Repository ndb = new FileRepository(db.getDirectory())) {
  722. assertEquals(rb2, ndb.resolve("refs/heads/new/name"));
  723. assertNull(ndb.resolve("refs/heads/b"));
  724. }
  725. }
  726. public void tryRenameWhenLocked(String toLock, String fromName,
  727. String toName, String headPointsTo) throws IOException {
  728. // setup
  729. writeSymref(Constants.HEAD, headPointsTo);
  730. ObjectId oldfromId = db.resolve(fromName);
  731. ObjectId oldHeadId = db.resolve(Constants.HEAD);
  732. writeReflog(db, oldfromId, "Just a message", fromName);
  733. List<ReflogEntry> oldFromLog = db
  734. .getReflogReader(fromName).getReverseEntries();
  735. List<ReflogEntry> oldHeadLog = oldHeadId != null ? db
  736. .getReflogReader(Constants.HEAD).getReverseEntries() : null;
  737. assertTrue("internal check, we have a log", new File(db.getDirectory(),
  738. "logs/" + fromName).exists());
  739. // "someone" has branch X locked
  740. LockFile lockFile = new LockFile(new File(db.getDirectory(), toLock));
  741. try {
  742. assertTrue(lockFile.lock());
  743. // Now this is our test
  744. RefRename renameRef = db.renameRef(fromName, toName);
  745. Result result = renameRef.rename();
  746. assertEquals(Result.LOCK_FAILURE, result);
  747. // Check that the involved refs are the same despite the failure
  748. assertExists(false, toName);
  749. if (!toLock.equals(toName))
  750. assertExists(false, toName + LOCK_SUFFIX);
  751. assertExists(true, toLock + LOCK_SUFFIX);
  752. if (!toLock.equals(fromName))
  753. assertExists(false, "logs/" + fromName + LOCK_SUFFIX);
  754. assertExists(false, "logs/" + toName + LOCK_SUFFIX);
  755. assertEquals(oldHeadId, db.resolve(Constants.HEAD));
  756. assertEquals(oldfromId, db.resolve(fromName));
  757. assertNull(db.resolve(toName));
  758. assertEquals(oldFromLog.toString(), db.getReflogReader(fromName)
  759. .getReverseEntries().toString());
  760. if (oldHeadId != null && oldHeadLog != null)
  761. assertEquals(oldHeadLog.toString(), db.getReflogReader(
  762. Constants.HEAD).getReverseEntries().toString());
  763. } finally {
  764. lockFile.unlock();
  765. }
  766. }
  767. private void assertExists(boolean positive, String toName) {
  768. assertEquals(toName + (positive ? " " : " does not ") + "exist",
  769. positive, new File(db.getDirectory(), toName).exists());
  770. }
  771. @Test
  772. public void testRenameBranchCannotLockAFileHEADisFromLockHEAD()
  773. throws IOException {
  774. tryRenameWhenLocked("HEAD", "refs/heads/b", "refs/heads/new/name",
  775. "refs/heads/b");
  776. }
  777. @Test
  778. public void testRenameBranchCannotLockAFileHEADisFromLockFrom()
  779. throws IOException {
  780. tryRenameWhenLocked("refs/heads/b", "refs/heads/b",
  781. "refs/heads/new/name", "refs/heads/b");
  782. }
  783. @Test
  784. public void testRenameBranchCannotLockAFileHEADisFromLockTo()
  785. throws IOException {
  786. tryRenameWhenLocked("refs/heads/new/name", "refs/heads/b",
  787. "refs/heads/new/name", "refs/heads/b");
  788. }
  789. @Test
  790. public void testRenameBranchCannotLockAFileHEADisToLockFrom()
  791. throws IOException {
  792. tryRenameWhenLocked("refs/heads/b", "refs/heads/b",
  793. "refs/heads/new/name", "refs/heads/new/name");
  794. }
  795. @Test
  796. public void testRenameBranchCannotLockAFileHEADisToLockTo()
  797. throws IOException {
  798. tryRenameWhenLocked("refs/heads/new/name", "refs/heads/b",
  799. "refs/heads/new/name", "refs/heads/new/name");
  800. }
  801. @Test
  802. public void testRenameBranchCannotLockAFileHEADisOtherLockFrom()
  803. throws IOException {
  804. tryRenameWhenLocked("refs/heads/b", "refs/heads/b",
  805. "refs/heads/new/name", "refs/heads/a");
  806. }
  807. @Test
  808. public void testRenameBranchCannotLockAFileHEADisOtherLockTo()
  809. throws IOException {
  810. tryRenameWhenLocked("refs/heads/new/name", "refs/heads/b",
  811. "refs/heads/new/name", "refs/heads/a");
  812. }
  813. @Test
  814. public void testRenameRefNameColission1avoided() throws IOException {
  815. // setup
  816. ObjectId rb = db.resolve("refs/heads/b");
  817. writeSymref(Constants.HEAD, "refs/heads/a");
  818. RefUpdate updateRef = db.updateRef("refs/heads/a");
  819. updateRef.setNewObjectId(rb);
  820. updateRef.setRefLogMessage("Setup", false);
  821. assertEquals(Result.FAST_FORWARD, updateRef.update());
  822. ObjectId oldHead = db.resolve(Constants.HEAD);
  823. assertEquals(oldHead, rb); // assumption for this test
  824. writeReflog(db, rb, "Just a message", "refs/heads/a");
  825. assertTrue("internal check, we have a log", new File(db.getDirectory(),
  826. "logs/refs/heads/a").exists());
  827. // Now this is our test
  828. RefRename renameRef = db.renameRef("refs/heads/a", "refs/heads/a/b");
  829. Result result = renameRef.rename();
  830. assertEquals(Result.RENAMED, result);
  831. assertNull(db.resolve("refs/heads/a"));
  832. assertEquals(rb, db.resolve("refs/heads/a/b"));
  833. assertEquals(3, db.getReflogReader("a/b").getReverseEntries().size());
  834. assertEquals("Branch: renamed a to a/b", db.getReflogReader("a/b")
  835. .getReverseEntries().get(0).getComment());
  836. assertEquals("Just a message", db.getReflogReader("a/b")
  837. .getReverseEntries().get(1).getComment());
  838. assertEquals("Setup", db.getReflogReader("a/b").getReverseEntries()
  839. .get(2).getComment());
  840. // same thing was logged to HEAD
  841. assertEquals("Branch: renamed a to a/b", db.getReflogReader("HEAD")
  842. .getReverseEntries().get(0).getComment());
  843. }
  844. @Test
  845. public void testRenameRefNameColission2avoided() throws IOException {
  846. // setup
  847. ObjectId rb = db.resolve("refs/heads/b");
  848. writeSymref(Constants.HEAD, "refs/heads/prefix/a");
  849. RefUpdate updateRef = db.updateRef("refs/heads/prefix/a");
  850. updateRef.setNewObjectId(rb);
  851. updateRef.setRefLogMessage("Setup", false);
  852. updateRef.setForceUpdate(true);
  853. assertEquals(Result.FORCED, updateRef.update());
  854. ObjectId oldHead = db.resolve(Constants.HEAD);
  855. assertEquals(oldHead, rb); // assumption for this test
  856. writeReflog(db, rb, "Just a message", "refs/heads/prefix/a");
  857. assertTrue("internal check, we have a log", new File(db.getDirectory(),
  858. "logs/refs/heads/prefix/a").exists());
  859. // Now this is our test
  860. RefRename renameRef = db.renameRef("refs/heads/prefix/a",
  861. "refs/heads/prefix");
  862. Result result = renameRef.rename();
  863. assertEquals(Result.RENAMED, result);
  864. assertNull(db.resolve("refs/heads/prefix/a"));
  865. assertEquals(rb, db.resolve("refs/heads/prefix"));
  866. assertEquals(3, db.getReflogReader("prefix").getReverseEntries().size());
  867. assertEquals("Branch: renamed prefix/a to prefix", db.getReflogReader(
  868. "prefix").getReverseEntries().get(0).getComment());
  869. assertEquals("Just a message", db.getReflogReader("prefix")
  870. .getReverseEntries().get(1).getComment());
  871. assertEquals("Setup", db.getReflogReader("prefix").getReverseEntries()
  872. .get(2).getComment());
  873. assertEquals("Branch: renamed prefix/a to prefix", db.getReflogReader(
  874. "HEAD").getReverseEntries().get(0).getComment());
  875. }
  876. @Test
  877. public void testCreateMissingObject() throws IOException {
  878. String name = "refs/heads/abc";
  879. ObjectId bad =
  880. ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
  881. RefUpdate ru = db.updateRef(name);
  882. ru.setNewObjectId(bad);
  883. Result update = ru.update();
  884. assertEquals(Result.REJECTED_MISSING_OBJECT, update);
  885. Ref ref = db.exactRef(name);
  886. assertNull(ref);
  887. }
  888. @Test
  889. public void testUpdateMissingObject() throws IOException {
  890. String name = "refs/heads/abc";
  891. RefUpdate ru = updateRef(name);
  892. Result update = ru.update();
  893. assertEquals(Result.NEW, update);
  894. ObjectId oldId = ru.getNewObjectId();
  895. ObjectId bad =
  896. ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
  897. ru = db.updateRef(name);
  898. ru.setNewObjectId(bad);
  899. update = ru.update();
  900. assertEquals(Result.REJECTED_MISSING_OBJECT, update);
  901. Ref ref = db.exactRef(name);
  902. assertNotNull(ref);
  903. assertEquals(oldId, ref.getObjectId());
  904. }
  905. @Test
  906. public void testForceUpdateMissingObject() throws IOException {
  907. String name = "refs/heads/abc";
  908. RefUpdate ru = updateRef(name);
  909. Result update = ru.update();
  910. assertEquals(Result.NEW, update);
  911. ObjectId oldId = ru.getNewObjectId();
  912. ObjectId bad =
  913. ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
  914. ru = db.updateRef(name);
  915. ru.setNewObjectId(bad);
  916. update = ru.forceUpdate();
  917. assertEquals(Result.REJECTED_MISSING_OBJECT, update);
  918. Ref ref = db.exactRef(name);
  919. assertNotNull(ref);
  920. assertEquals(oldId, ref.getObjectId());
  921. }
  922. private static void writeReflog(Repository db, ObjectId newId, String msg,
  923. String refName) throws IOException {
  924. RefDirectory refs = (RefDirectory) db.getRefDatabase();
  925. RefDirectoryUpdate update = refs.newUpdate(refName, true);
  926. update.setNewObjectId(newId);
  927. refs.log(false, update, msg, true);
  928. }
  929. private static class SubclassedId extends ObjectId {
  930. SubclassedId(AnyObjectId src) {
  931. super(src);
  932. }
  933. }
  934. }