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 32KB

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 年之前
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 年之前
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 年之前
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 年之前
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 年之前
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 年之前
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 年之前
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 年之前
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 年之前
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 年之前
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 年之前
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 年之前
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 年之前
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 年之前
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 年之前
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 年之前
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 年之前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898
  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.junit.Assert.assertEquals;
  48. import static org.junit.Assert.assertFalse;
  49. import static org.junit.Assert.assertNotNull;
  50. import static org.junit.Assert.assertNotSame;
  51. import static org.junit.Assert.assertNull;
  52. import static org.junit.Assert.assertSame;
  53. import static org.junit.Assert.assertTrue;
  54. import static org.junit.Assert.fail;
  55. import java.io.File;
  56. import java.io.IOException;
  57. import java.util.List;
  58. import java.util.Map;
  59. import java.util.Map.Entry;
  60. import org.eclipse.jgit.lib.AnyObjectId;
  61. import org.eclipse.jgit.lib.Constants;
  62. import org.eclipse.jgit.lib.ObjectId;
  63. import org.eclipse.jgit.lib.PersonIdent;
  64. import org.eclipse.jgit.lib.Ref;
  65. import org.eclipse.jgit.lib.RefRename;
  66. import org.eclipse.jgit.lib.RefUpdate;
  67. import org.eclipse.jgit.lib.RefUpdate.Result;
  68. import org.eclipse.jgit.lib.ReflogEntry;
  69. import org.eclipse.jgit.lib.ReflogReader;
  70. import org.eclipse.jgit.lib.Repository;
  71. import org.eclipse.jgit.revwalk.RevCommit;
  72. import org.eclipse.jgit.revwalk.RevWalk;
  73. import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
  74. import org.junit.Test;
  75. public class RefUpdateTest extends SampleDataRepositoryTestCase {
  76. private void writeSymref(String src, String dst) throws IOException {
  77. RefUpdate u = db.updateRef(src);
  78. switch (u.link(dst)) {
  79. case NEW:
  80. case FORCED:
  81. case NO_CHANGE:
  82. break;
  83. default:
  84. fail("link " + src + " to " + dst);
  85. }
  86. }
  87. private RefUpdate updateRef(final String name) throws IOException {
  88. final RefUpdate ref = db.updateRef(name);
  89. ref.setNewObjectId(db.resolve(Constants.HEAD));
  90. return ref;
  91. }
  92. private void delete(final RefUpdate ref, final Result expected)
  93. throws IOException {
  94. delete(ref, expected, true, true);
  95. }
  96. private void delete(final RefUpdate ref, final Result expected,
  97. final boolean exists, final boolean removed) throws IOException {
  98. assertEquals(exists, db.getAllRefs().containsKey(ref.getName()));
  99. assertEquals(expected, ref.delete());
  100. assertEquals(!removed, db.getAllRefs().containsKey(ref.getName()));
  101. }
  102. @Test
  103. public void testNoCacheObjectIdSubclass() throws IOException {
  104. final String newRef = "refs/heads/abc";
  105. final RefUpdate ru = updateRef(newRef);
  106. final SubclassedId newid = new SubclassedId(ru.getNewObjectId());
  107. ru.setNewObjectId(newid);
  108. Result update = ru.update();
  109. assertEquals(Result.NEW, update);
  110. final Ref r = db.getAllRefs().get(newRef);
  111. assertNotNull(r);
  112. assertEquals(newRef, r.getName());
  113. assertNotNull(r.getObjectId());
  114. assertNotSame(newid, r.getObjectId());
  115. assertSame(ObjectId.class, r.getObjectId().getClass());
  116. assertEquals(newid, r.getObjectId());
  117. List<ReflogEntry> reverseEntries1 = db
  118. .getReflogReader("refs/heads/abc").getReverseEntries();
  119. ReflogEntry entry1 = reverseEntries1.get(0);
  120. assertEquals(1, reverseEntries1.size());
  121. assertEquals(ObjectId.zeroId(), entry1.getOldId());
  122. assertEquals(r.getObjectId(), entry1.getNewId());
  123. assertEquals(new PersonIdent(db).toString(), entry1.getWho().toString());
  124. assertEquals("", entry1.getComment());
  125. List<ReflogEntry> reverseEntries2 = db.getReflogReader("HEAD")
  126. .getReverseEntries();
  127. assertEquals(0, reverseEntries2.size());
  128. }
  129. @Test
  130. public void testNewNamespaceConflictWithLoosePrefixNameExists()
  131. throws IOException {
  132. final String newRef = "refs/heads/z";
  133. final RefUpdate ru = updateRef(newRef);
  134. Result update = ru.update();
  135. assertEquals(Result.NEW, update);
  136. // end setup
  137. final String newRef2 = "refs/heads/z/a";
  138. final RefUpdate ru2 = updateRef(newRef2);
  139. Result update2 = ru2.update();
  140. assertEquals(Result.LOCK_FAILURE, update2);
  141. assertEquals(1, db.getReflogReader("refs/heads/z").getReverseEntries().size());
  142. assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
  143. }
  144. @Test
  145. public void testNewNamespaceConflictWithPackedPrefixNameExists()
  146. throws IOException {
  147. final String newRef = "refs/heads/master/x";
  148. final RefUpdate ru = updateRef(newRef);
  149. Result update = ru.update();
  150. assertEquals(Result.LOCK_FAILURE, update);
  151. assertNull(db.getReflogReader("refs/heads/master/x"));
  152. assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
  153. }
  154. @Test
  155. public void testNewNamespaceConflictWithLoosePrefixOfExisting()
  156. throws IOException {
  157. final String newRef = "refs/heads/z/a";
  158. final RefUpdate ru = updateRef(newRef);
  159. Result update = ru.update();
  160. assertEquals(Result.NEW, update);
  161. // end setup
  162. final String newRef2 = "refs/heads/z";
  163. final RefUpdate ru2 = updateRef(newRef2);
  164. Result update2 = ru2.update();
  165. assertEquals(Result.LOCK_FAILURE, update2);
  166. assertEquals(1, db.getReflogReader("refs/heads/z/a").getReverseEntries().size());
  167. assertNull(db.getReflogReader("refs/heads/z"));
  168. assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
  169. }
  170. @Test
  171. public void testNewNamespaceConflictWithPackedPrefixOfExisting()
  172. throws IOException {
  173. final String newRef = "refs/heads/prefix";
  174. final RefUpdate ru = updateRef(newRef);
  175. Result update = ru.update();
  176. assertEquals(Result.LOCK_FAILURE, update);
  177. assertNull(db.getReflogReader("refs/heads/prefix"));
  178. assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
  179. }
  180. /**
  181. * Delete a ref that is pointed to by HEAD
  182. *
  183. * @throws IOException
  184. */
  185. @Test
  186. public void testDeleteHEADreferencedRef() throws IOException {
  187. ObjectId pid = db.resolve("refs/heads/master^");
  188. RefUpdate updateRef = db.updateRef("refs/heads/master");
  189. updateRef.setNewObjectId(pid);
  190. updateRef.setForceUpdate(true);
  191. Result update = updateRef.update();
  192. assertEquals(Result.FORCED, update); // internal
  193. RefUpdate updateRef2 = db.updateRef("refs/heads/master");
  194. Result delete = updateRef2.delete();
  195. assertEquals(Result.REJECTED_CURRENT_BRANCH, delete);
  196. assertEquals(pid, db.resolve("refs/heads/master"));
  197. assertEquals(1,db.getReflogReader("refs/heads/master").getReverseEntries().size());
  198. assertEquals(0,db.getReflogReader("HEAD").getReverseEntries().size());
  199. }
  200. @Test
  201. public void testLooseDelete() throws IOException {
  202. final String newRef = "refs/heads/abc";
  203. RefUpdate ref = updateRef(newRef);
  204. ref.update(); // create loose ref
  205. ref = updateRef(newRef); // refresh
  206. delete(ref, Result.NO_CHANGE);
  207. assertNull(db.getReflogReader("refs/heads/abc"));
  208. }
  209. @Test
  210. public void testDeleteHead() throws IOException {
  211. final RefUpdate ref = updateRef(Constants.HEAD);
  212. delete(ref, Result.REJECTED_CURRENT_BRANCH, true, false);
  213. assertEquals(0, db.getReflogReader("refs/heads/master").getReverseEntries().size());
  214. assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
  215. }
  216. /**
  217. * Delete a loose ref and make sure the directory in refs is deleted too,
  218. * and the reflog dir too
  219. *
  220. * @throws IOException
  221. */
  222. @Test
  223. public void testDeleteLooseAndItsDirectory() throws IOException {
  224. ObjectId pid = db.resolve("refs/heads/c^");
  225. RefUpdate updateRef = db.updateRef("refs/heads/z/c");
  226. updateRef.setNewObjectId(pid);
  227. updateRef.setForceUpdate(true);
  228. updateRef.setRefLogMessage("new test ref", false);
  229. Result update = updateRef.update();
  230. assertEquals(Result.NEW, update); // internal
  231. assertTrue(new File(db.getDirectory(), Constants.R_HEADS + "z")
  232. .exists());
  233. assertTrue(new File(db.getDirectory(), "logs/refs/heads/z").exists());
  234. // The real test here
  235. RefUpdate updateRef2 = db.updateRef("refs/heads/z/c");
  236. updateRef2.setForceUpdate(true);
  237. Result delete = updateRef2.delete();
  238. assertEquals(Result.FORCED, delete);
  239. assertNull(db.resolve("refs/heads/z/c"));
  240. assertFalse(new File(db.getDirectory(), Constants.R_HEADS + "z")
  241. .exists());
  242. assertFalse(new File(db.getDirectory(), "logs/refs/heads/z").exists());
  243. }
  244. @Test
  245. public void testDeleteNotFound() throws IOException {
  246. final RefUpdate ref = updateRef("refs/heads/xyz");
  247. delete(ref, Result.NEW, false, true);
  248. }
  249. @Test
  250. public void testDeleteFastForward() throws IOException {
  251. final RefUpdate ref = updateRef("refs/heads/a");
  252. delete(ref, Result.FAST_FORWARD);
  253. }
  254. @Test
  255. public void testDeleteForce() throws IOException {
  256. final RefUpdate ref = db.updateRef("refs/heads/b");
  257. ref.setNewObjectId(db.resolve("refs/heads/a"));
  258. delete(ref, Result.REJECTED, true, false);
  259. ref.setForceUpdate(true);
  260. delete(ref, Result.FORCED);
  261. }
  262. @Test
  263. public void testDeleteWithoutHead() throws IOException {
  264. // Prepare repository without HEAD
  265. RefUpdate refUpdate = db.updateRef(Constants.HEAD, true);
  266. refUpdate.setForceUpdate(true);
  267. refUpdate.setNewObjectId(ObjectId.zeroId());
  268. Result updateResult = refUpdate.update();
  269. assertEquals(Result.FORCED, updateResult);
  270. Result deleteHeadResult = db.updateRef(Constants.HEAD).delete();
  271. assertEquals(Result.NO_CHANGE, deleteHeadResult);
  272. // Any result is ok as long as it's not an NPE
  273. db.updateRef(Constants.R_HEADS + "master").delete();
  274. }
  275. @Test
  276. public void testRefKeySameAsName() {
  277. Map<String, Ref> allRefs = db.getAllRefs();
  278. for (Entry<String, Ref> e : allRefs.entrySet()) {
  279. assertEquals(e.getKey(), e.getValue().getName());
  280. }
  281. }
  282. /**
  283. * Try modify a ref forward, fast forward
  284. *
  285. * @throws IOException
  286. */
  287. @Test
  288. public void testUpdateRefForward() throws IOException {
  289. ObjectId ppid = db.resolve("refs/heads/master^");
  290. ObjectId pid = db.resolve("refs/heads/master");
  291. RefUpdate updateRef = db.updateRef("refs/heads/master");
  292. updateRef.setNewObjectId(ppid);
  293. updateRef.setForceUpdate(true);
  294. Result update = updateRef.update();
  295. assertEquals(Result.FORCED, update);
  296. assertEquals(ppid, db.resolve("refs/heads/master"));
  297. // real test
  298. RefUpdate updateRef2 = db.updateRef("refs/heads/master");
  299. updateRef2.setNewObjectId(pid);
  300. Result update2 = updateRef2.update();
  301. assertEquals(Result.FAST_FORWARD, update2);
  302. assertEquals(pid, db.resolve("refs/heads/master"));
  303. }
  304. /**
  305. * Update the HEAD ref. Only it should be changed, not what it points to.
  306. *
  307. * @throws Exception
  308. */
  309. @Test
  310. public void testUpdateRefDetached() throws Exception {
  311. ObjectId pid = db.resolve("refs/heads/master");
  312. ObjectId ppid = db.resolve("refs/heads/master^");
  313. RefUpdate updateRef = db.updateRef("HEAD", true);
  314. updateRef.setForceUpdate(true);
  315. updateRef.setNewObjectId(ppid);
  316. Result update = updateRef.update();
  317. assertEquals(Result.FORCED, update);
  318. assertEquals(ppid, db.resolve("HEAD"));
  319. Ref ref = db.getRef("HEAD");
  320. assertEquals("HEAD", ref.getName());
  321. assertTrue("is detached", !ref.isSymbolic());
  322. // the branch HEAD referred to is left untouched
  323. assertEquals(pid, db.resolve("refs/heads/master"));
  324. ReflogReader reflogReader = db.getReflogReader("HEAD");
  325. ReflogEntry e = reflogReader.getReverseEntries().get(0);
  326. assertEquals(pid, e.getOldId());
  327. assertEquals(ppid, e.getNewId());
  328. assertEquals("GIT_COMMITTER_EMAIL", e.getWho().getEmailAddress());
  329. assertEquals("GIT_COMMITTER_NAME", e.getWho().getName());
  330. assertEquals(1250379778000L, e.getWho().getWhen().getTime());
  331. }
  332. /**
  333. * Update the HEAD ref when the referenced branch is unborn
  334. *
  335. * @throws Exception
  336. */
  337. @Test
  338. public void testUpdateRefDetachedUnbornHead() throws Exception {
  339. ObjectId ppid = db.resolve("refs/heads/master^");
  340. writeSymref("HEAD", "refs/heads/unborn");
  341. RefUpdate updateRef = db.updateRef("HEAD", true);
  342. updateRef.setForceUpdate(true);
  343. updateRef.setNewObjectId(ppid);
  344. Result update = updateRef.update();
  345. assertEquals(Result.NEW, update);
  346. assertEquals(ppid, db.resolve("HEAD"));
  347. Ref ref = db.getRef("HEAD");
  348. assertEquals("HEAD", ref.getName());
  349. assertTrue("is detached", !ref.isSymbolic());
  350. // the branch HEAD referred to is left untouched
  351. assertNull(db.resolve("refs/heads/unborn"));
  352. ReflogReader reflogReader = db.getReflogReader("HEAD");
  353. ReflogEntry e = reflogReader.getReverseEntries().get(0);
  354. assertEquals(ObjectId.zeroId(), e.getOldId());
  355. assertEquals(ppid, e.getNewId());
  356. assertEquals("GIT_COMMITTER_EMAIL", e.getWho().getEmailAddress());
  357. assertEquals("GIT_COMMITTER_NAME", e.getWho().getName());
  358. assertEquals(1250379778000L, e.getWho().getWhen().getTime());
  359. }
  360. /**
  361. * Delete a ref that exists both as packed and loose. Make sure the ref
  362. * cannot be resolved after delete.
  363. *
  364. * @throws IOException
  365. */
  366. @Test
  367. public void testDeleteLoosePacked() throws IOException {
  368. ObjectId pid = db.resolve("refs/heads/c^");
  369. RefUpdate updateRef = db.updateRef("refs/heads/c");
  370. updateRef.setNewObjectId(pid);
  371. updateRef.setForceUpdate(true);
  372. Result update = updateRef.update();
  373. assertEquals(Result.FORCED, update); // internal
  374. // The real test here
  375. RefUpdate updateRef2 = db.updateRef("refs/heads/c");
  376. updateRef2.setForceUpdate(true);
  377. Result delete = updateRef2.delete();
  378. assertEquals(Result.FORCED, delete);
  379. assertNull(db.resolve("refs/heads/c"));
  380. }
  381. /**
  382. * Try modify a ref to same
  383. *
  384. * @throws IOException
  385. */
  386. @Test
  387. public void testUpdateRefNoChange() throws IOException {
  388. ObjectId pid = db.resolve("refs/heads/master");
  389. RefUpdate updateRef = db.updateRef("refs/heads/master");
  390. updateRef.setNewObjectId(pid);
  391. Result update = updateRef.update();
  392. assertEquals(Result.NO_CHANGE, update);
  393. assertEquals(pid, db.resolve("refs/heads/master"));
  394. }
  395. /**
  396. * Test case originating from
  397. * <a href="http://bugs.eclipse.org/285991">bug 285991</a>
  398. *
  399. * Make sure the in memory cache is updated properly after
  400. * update of symref. This one did not fail because the
  401. * ref was packed due to implementation issues.
  402. *
  403. * @throws Exception
  404. */
  405. @Test
  406. public void testRefsCacheAfterUpdate() throws Exception {
  407. // Do not use the defalt repo for this case.
  408. Map<String, Ref> allRefs = db.getAllRefs();
  409. ObjectId oldValue = db.resolve("HEAD");
  410. ObjectId newValue = db.resolve("HEAD^");
  411. // first make HEAD refer to loose ref
  412. RefUpdate updateRef = db.updateRef(Constants.HEAD);
  413. updateRef.setForceUpdate(true);
  414. updateRef.setNewObjectId(newValue);
  415. Result update = updateRef.update();
  416. assertEquals(Result.FORCED, update);
  417. // now update that ref
  418. updateRef = db.updateRef(Constants.HEAD);
  419. updateRef.setForceUpdate(true);
  420. updateRef.setNewObjectId(oldValue);
  421. update = updateRef.update();
  422. assertEquals(Result.FAST_FORWARD, update);
  423. allRefs = db.getAllRefs();
  424. Ref master = allRefs.get("refs/heads/master");
  425. Ref head = allRefs.get("HEAD");
  426. assertEquals("refs/heads/master", master.getName());
  427. assertEquals("HEAD", head.getName());
  428. assertTrue("is symbolic reference", head.isSymbolic());
  429. assertSame(master, head.getTarget());
  430. }
  431. /**
  432. * Test case originating from
  433. * <a href="http://bugs.eclipse.org/285991">bug 285991</a>
  434. *
  435. * Make sure the in memory cache is updated properly after
  436. * update of symref.
  437. *
  438. * @throws Exception
  439. */
  440. @Test
  441. public void testRefsCacheAfterUpdateLooseOnly() throws Exception {
  442. // Do not use the defalt repo for this case.
  443. Map<String, Ref> allRefs = db.getAllRefs();
  444. ObjectId oldValue = db.resolve("HEAD");
  445. writeSymref(Constants.HEAD, "refs/heads/newref");
  446. RefUpdate updateRef = db.updateRef(Constants.HEAD);
  447. updateRef.setForceUpdate(true);
  448. updateRef.setNewObjectId(oldValue);
  449. Result update = updateRef.update();
  450. assertEquals(Result.NEW, update);
  451. allRefs = db.getAllRefs();
  452. Ref head = allRefs.get("HEAD");
  453. Ref newref = allRefs.get("refs/heads/newref");
  454. assertEquals("refs/heads/newref", newref.getName());
  455. assertEquals("HEAD", head.getName());
  456. assertTrue("is symbolic reference", head.isSymbolic());
  457. assertSame(newref, head.getTarget());
  458. }
  459. /**
  460. * Try modify a ref, but get wrong expected old value
  461. *
  462. * @throws IOException
  463. */
  464. @Test
  465. public void testUpdateRefLockFailureWrongOldValue() throws IOException {
  466. ObjectId pid = db.resolve("refs/heads/master");
  467. RefUpdate updateRef = db.updateRef("refs/heads/master");
  468. updateRef.setNewObjectId(pid);
  469. updateRef.setExpectedOldObjectId(db.resolve("refs/heads/master^"));
  470. Result update = updateRef.update();
  471. assertEquals(Result.LOCK_FAILURE, update);
  472. assertEquals(pid, db.resolve("refs/heads/master"));
  473. }
  474. /**
  475. * Try modify a ref forward, fast forward, checking old value first
  476. *
  477. * @throws IOException
  478. */
  479. @Test
  480. public void testUpdateRefForwardWithCheck1() throws IOException {
  481. ObjectId ppid = db.resolve("refs/heads/master^");
  482. ObjectId pid = db.resolve("refs/heads/master");
  483. RefUpdate updateRef = db.updateRef("refs/heads/master");
  484. updateRef.setNewObjectId(ppid);
  485. updateRef.setForceUpdate(true);
  486. Result update = updateRef.update();
  487. assertEquals(Result.FORCED, update);
  488. assertEquals(ppid, db.resolve("refs/heads/master"));
  489. // real test
  490. RefUpdate updateRef2 = db.updateRef("refs/heads/master");
  491. updateRef2.setExpectedOldObjectId(ppid);
  492. updateRef2.setNewObjectId(pid);
  493. Result update2 = updateRef2.update();
  494. assertEquals(Result.FAST_FORWARD, update2);
  495. assertEquals(pid, db.resolve("refs/heads/master"));
  496. }
  497. /**
  498. * Try modify a ref forward, fast forward, checking old commit first
  499. *
  500. * @throws IOException
  501. */
  502. @Test
  503. public void testUpdateRefForwardWithCheck2() throws IOException {
  504. ObjectId ppid = db.resolve("refs/heads/master^");
  505. ObjectId pid = db.resolve("refs/heads/master");
  506. RefUpdate updateRef = db.updateRef("refs/heads/master");
  507. updateRef.setNewObjectId(ppid);
  508. updateRef.setForceUpdate(true);
  509. Result update = updateRef.update();
  510. assertEquals(Result.FORCED, update);
  511. assertEquals(ppid, db.resolve("refs/heads/master"));
  512. // real test
  513. RevCommit old = new RevWalk(db).parseCommit(ppid);
  514. RefUpdate updateRef2 = db.updateRef("refs/heads/master");
  515. updateRef2.setExpectedOldObjectId(old);
  516. updateRef2.setNewObjectId(pid);
  517. Result update2 = updateRef2.update();
  518. assertEquals(Result.FAST_FORWARD, update2);
  519. assertEquals(pid, db.resolve("refs/heads/master"));
  520. }
  521. /**
  522. * Try modify a ref that is locked
  523. *
  524. * @throws IOException
  525. */
  526. @Test
  527. public void testUpdateRefLockFailureLocked() throws IOException {
  528. ObjectId opid = db.resolve("refs/heads/master");
  529. ObjectId pid = db.resolve("refs/heads/master^");
  530. RefUpdate updateRef = db.updateRef("refs/heads/master");
  531. updateRef.setNewObjectId(pid);
  532. LockFile lockFile1 = new LockFile(new File(db.getDirectory(),
  533. "refs/heads/master"), db.getFS());
  534. try {
  535. assertTrue(lockFile1.lock()); // precondition to test
  536. Result update = updateRef.update();
  537. assertEquals(Result.LOCK_FAILURE, update);
  538. assertEquals(opid, db.resolve("refs/heads/master"));
  539. LockFile lockFile2 = new LockFile(new File(db.getDirectory(),"refs/heads/master"),
  540. db.getFS());
  541. assertFalse(lockFile2.lock()); // was locked, still is
  542. } finally {
  543. lockFile1.unlock();
  544. }
  545. }
  546. /**
  547. * Try to delete a ref. Delete requires force.
  548. *
  549. * @throws IOException
  550. */
  551. @Test
  552. public void testDeleteLoosePackedRejected() throws IOException {
  553. ObjectId pid = db.resolve("refs/heads/c^");
  554. ObjectId oldpid = db.resolve("refs/heads/c");
  555. RefUpdate updateRef = db.updateRef("refs/heads/c");
  556. updateRef.setNewObjectId(pid);
  557. Result update = updateRef.update();
  558. assertEquals(Result.REJECTED, update);
  559. assertEquals(oldpid, db.resolve("refs/heads/c"));
  560. }
  561. @Test
  562. public void testRenameBranchNoPreviousLog() throws IOException {
  563. assertFalse("precondition, no log on old branchg", new File(db
  564. .getDirectory(), "logs/refs/heads/b").exists());
  565. ObjectId rb = db.resolve("refs/heads/b");
  566. ObjectId oldHead = db.resolve(Constants.HEAD);
  567. assertFalse(rb.equals(oldHead)); // assumption for this test
  568. RefRename renameRef = db.renameRef("refs/heads/b",
  569. "refs/heads/new/name");
  570. Result result = renameRef.rename();
  571. assertEquals(Result.RENAMED, result);
  572. assertEquals(rb, db.resolve("refs/heads/new/name"));
  573. assertNull(db.resolve("refs/heads/b"));
  574. assertEquals(1, db.getReflogReader("new/name").getReverseEntries().size());
  575. assertEquals("Branch: renamed b to new/name", db.getReflogReader("new/name")
  576. .getLastEntry().getComment());
  577. assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists());
  578. assertEquals(oldHead, db.resolve(Constants.HEAD)); // unchanged
  579. }
  580. @Test
  581. public void testRenameBranchHasPreviousLog() throws IOException {
  582. ObjectId rb = db.resolve("refs/heads/b");
  583. ObjectId oldHead = db.resolve(Constants.HEAD);
  584. assertFalse("precondition for this test, branch b != HEAD", rb
  585. .equals(oldHead));
  586. writeReflog(db, rb, "Just a message", "refs/heads/b");
  587. assertTrue("log on old branch", new File(db.getDirectory(),
  588. "logs/refs/heads/b").exists());
  589. RefRename renameRef = db.renameRef("refs/heads/b",
  590. "refs/heads/new/name");
  591. Result result = renameRef.rename();
  592. assertEquals(Result.RENAMED, result);
  593. assertEquals(rb, db.resolve("refs/heads/new/name"));
  594. assertNull(db.resolve("refs/heads/b"));
  595. assertEquals(2, db.getReflogReader("new/name").getReverseEntries().size());
  596. assertEquals("Branch: renamed b to new/name", db.getReflogReader("new/name")
  597. .getLastEntry().getComment());
  598. assertEquals("Just a message", db.getReflogReader("new/name")
  599. .getReverseEntries().get(1).getComment());
  600. assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists());
  601. assertEquals(oldHead, db.resolve(Constants.HEAD)); // unchanged
  602. }
  603. @Test
  604. public void testRenameCurrentBranch() throws IOException {
  605. ObjectId rb = db.resolve("refs/heads/b");
  606. writeSymref(Constants.HEAD, "refs/heads/b");
  607. ObjectId oldHead = db.resolve(Constants.HEAD);
  608. assertEquals("internal test condition, b == HEAD", oldHead, rb);
  609. writeReflog(db, rb, "Just a message", "refs/heads/b");
  610. assertTrue("log on old branch", new File(db.getDirectory(),
  611. "logs/refs/heads/b").exists());
  612. RefRename renameRef = db.renameRef("refs/heads/b",
  613. "refs/heads/new/name");
  614. Result result = renameRef.rename();
  615. assertEquals(Result.RENAMED, result);
  616. assertEquals(rb, db.resolve("refs/heads/new/name"));
  617. assertNull(db.resolve("refs/heads/b"));
  618. assertEquals("Branch: renamed b to new/name", db.getReflogReader(
  619. "new/name").getLastEntry().getComment());
  620. assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists());
  621. assertEquals(rb, db.resolve(Constants.HEAD));
  622. assertEquals(2, db.getReflogReader("new/name").getReverseEntries().size());
  623. assertEquals("Branch: renamed b to new/name", db.getReflogReader("new/name").getReverseEntries().get(0).getComment());
  624. assertEquals("Just a message", db.getReflogReader("new/name").getReverseEntries().get(1).getComment());
  625. }
  626. @Test
  627. public void testRenameBranchAlsoInPack() throws IOException {
  628. ObjectId rb = db.resolve("refs/heads/b");
  629. ObjectId rb2 = db.resolve("refs/heads/b~1");
  630. assertEquals(Ref.Storage.PACKED, db.getRef("refs/heads/b").getStorage());
  631. RefUpdate updateRef = db.updateRef("refs/heads/b");
  632. updateRef.setNewObjectId(rb2);
  633. updateRef.setForceUpdate(true);
  634. Result update = updateRef.update();
  635. assertEquals("internal check new ref is loose", Result.FORCED, update);
  636. assertEquals(Ref.Storage.LOOSE, db.getRef("refs/heads/b").getStorage());
  637. writeReflog(db, rb, "Just a message", "refs/heads/b");
  638. assertTrue("log on old branch", new File(db.getDirectory(),
  639. "logs/refs/heads/b").exists());
  640. RefRename renameRef = db.renameRef("refs/heads/b",
  641. "refs/heads/new/name");
  642. Result result = renameRef.rename();
  643. assertEquals(Result.RENAMED, result);
  644. assertEquals(rb2, db.resolve("refs/heads/new/name"));
  645. assertNull(db.resolve("refs/heads/b"));
  646. assertEquals("Branch: renamed b to new/name", db.getReflogReader(
  647. "new/name").getLastEntry().getComment());
  648. assertEquals(3, db.getReflogReader("refs/heads/new/name").getReverseEntries().size());
  649. assertEquals("Branch: renamed b to new/name", db.getReflogReader("refs/heads/new/name").getReverseEntries().get(0).getComment());
  650. assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
  651. // make sure b's log file is gone too.
  652. assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists());
  653. // Create new Repository instance, to reread caches and make sure our
  654. // assumptions are persistent.
  655. Repository ndb = new FileRepository(db.getDirectory());
  656. assertEquals(rb2, ndb.resolve("refs/heads/new/name"));
  657. assertNull(ndb.resolve("refs/heads/b"));
  658. }
  659. public void tryRenameWhenLocked(String toLock, String fromName,
  660. String toName, String headPointsTo) throws IOException {
  661. // setup
  662. writeSymref(Constants.HEAD, headPointsTo);
  663. ObjectId oldfromId = db.resolve(fromName);
  664. ObjectId oldHeadId = db.resolve(Constants.HEAD);
  665. writeReflog(db, oldfromId, "Just a message", fromName);
  666. List<ReflogEntry> oldFromLog = db
  667. .getReflogReader(fromName).getReverseEntries();
  668. List<ReflogEntry> oldHeadLog = oldHeadId != null ? db
  669. .getReflogReader(Constants.HEAD).getReverseEntries() : null;
  670. assertTrue("internal check, we have a log", new File(db.getDirectory(),
  671. "logs/" + fromName).exists());
  672. // "someone" has branch X locked
  673. LockFile lockFile = new LockFile(new File(db.getDirectory(), toLock),
  674. db.getFS());
  675. try {
  676. assertTrue(lockFile.lock());
  677. // Now this is our test
  678. RefRename renameRef = db.renameRef(fromName, toName);
  679. Result result = renameRef.rename();
  680. assertEquals(Result.LOCK_FAILURE, result);
  681. // Check that the involved refs are the same despite the failure
  682. assertExists(false, toName);
  683. if (!toLock.equals(toName))
  684. assertExists(false, toName + ".lock");
  685. assertExists(true, toLock + ".lock");
  686. if (!toLock.equals(fromName))
  687. assertExists(false, "logs/" + fromName + ".lock");
  688. assertExists(false, "logs/" + toName + ".lock");
  689. assertEquals(oldHeadId, db.resolve(Constants.HEAD));
  690. assertEquals(oldfromId, db.resolve(fromName));
  691. assertNull(db.resolve(toName));
  692. assertEquals(oldFromLog.toString(), db.getReflogReader(fromName)
  693. .getReverseEntries().toString());
  694. if (oldHeadId != null)
  695. assertEquals(oldHeadLog.toString(), db.getReflogReader(
  696. Constants.HEAD).getReverseEntries().toString());
  697. } finally {
  698. lockFile.unlock();
  699. }
  700. }
  701. private void assertExists(boolean positive, String toName) {
  702. assertEquals(toName + (positive ? " " : " does not ") + "exist",
  703. positive, new File(db.getDirectory(), toName).exists());
  704. }
  705. @Test
  706. public void testRenameBranchCannotLockAFileHEADisFromLockHEAD()
  707. throws IOException {
  708. tryRenameWhenLocked("HEAD", "refs/heads/b", "refs/heads/new/name",
  709. "refs/heads/b");
  710. }
  711. @Test
  712. public void testRenameBranchCannotLockAFileHEADisFromLockFrom()
  713. throws IOException {
  714. tryRenameWhenLocked("refs/heads/b", "refs/heads/b",
  715. "refs/heads/new/name", "refs/heads/b");
  716. }
  717. @Test
  718. public void testRenameBranchCannotLockAFileHEADisFromLockTo()
  719. throws IOException {
  720. tryRenameWhenLocked("refs/heads/new/name", "refs/heads/b",
  721. "refs/heads/new/name", "refs/heads/b");
  722. }
  723. @Test
  724. public void testRenameBranchCannotLockAFileHEADisToLockFrom()
  725. throws IOException {
  726. tryRenameWhenLocked("refs/heads/b", "refs/heads/b",
  727. "refs/heads/new/name", "refs/heads/new/name");
  728. }
  729. @Test
  730. public void testRenameBranchCannotLockAFileHEADisToLockTo()
  731. throws IOException {
  732. tryRenameWhenLocked("refs/heads/new/name", "refs/heads/b",
  733. "refs/heads/new/name", "refs/heads/new/name");
  734. }
  735. @Test
  736. public void testRenameBranchCannotLockAFileHEADisOtherLockFrom()
  737. throws IOException {
  738. tryRenameWhenLocked("refs/heads/b", "refs/heads/b",
  739. "refs/heads/new/name", "refs/heads/a");
  740. }
  741. @Test
  742. public void testRenameBranchCannotLockAFileHEADisOtherLockTo()
  743. throws IOException {
  744. tryRenameWhenLocked("refs/heads/new/name", "refs/heads/b",
  745. "refs/heads/new/name", "refs/heads/a");
  746. }
  747. @Test
  748. public void testRenameRefNameColission1avoided() throws IOException {
  749. // setup
  750. ObjectId rb = db.resolve("refs/heads/b");
  751. writeSymref(Constants.HEAD, "refs/heads/a");
  752. RefUpdate updateRef = db.updateRef("refs/heads/a");
  753. updateRef.setNewObjectId(rb);
  754. updateRef.setRefLogMessage("Setup", false);
  755. assertEquals(Result.FAST_FORWARD, updateRef.update());
  756. ObjectId oldHead = db.resolve(Constants.HEAD);
  757. assertEquals(oldHead, rb); // assumption for this test
  758. writeReflog(db, rb, "Just a message", "refs/heads/a");
  759. assertTrue("internal check, we have a log", new File(db.getDirectory(),
  760. "logs/refs/heads/a").exists());
  761. // Now this is our test
  762. RefRename renameRef = db.renameRef("refs/heads/a", "refs/heads/a/b");
  763. Result result = renameRef.rename();
  764. assertEquals(Result.RENAMED, result);
  765. assertNull(db.resolve("refs/heads/a"));
  766. assertEquals(rb, db.resolve("refs/heads/a/b"));
  767. assertEquals(3, db.getReflogReader("a/b").getReverseEntries().size());
  768. assertEquals("Branch: renamed a to a/b", db.getReflogReader("a/b")
  769. .getReverseEntries().get(0).getComment());
  770. assertEquals("Just a message", db.getReflogReader("a/b")
  771. .getReverseEntries().get(1).getComment());
  772. assertEquals("Setup", db.getReflogReader("a/b").getReverseEntries()
  773. .get(2).getComment());
  774. // same thing was logged to HEAD
  775. assertEquals("Branch: renamed a to a/b", db.getReflogReader("HEAD")
  776. .getReverseEntries().get(0).getComment());
  777. }
  778. @Test
  779. public void testRenameRefNameColission2avoided() throws IOException {
  780. // setup
  781. ObjectId rb = db.resolve("refs/heads/b");
  782. writeSymref(Constants.HEAD, "refs/heads/prefix/a");
  783. RefUpdate updateRef = db.updateRef("refs/heads/prefix/a");
  784. updateRef.setNewObjectId(rb);
  785. updateRef.setRefLogMessage("Setup", false);
  786. updateRef.setForceUpdate(true);
  787. assertEquals(Result.FORCED, updateRef.update());
  788. ObjectId oldHead = db.resolve(Constants.HEAD);
  789. assertEquals(oldHead, rb); // assumption for this test
  790. writeReflog(db, rb, "Just a message", "refs/heads/prefix/a");
  791. assertTrue("internal check, we have a log", new File(db.getDirectory(),
  792. "logs/refs/heads/prefix/a").exists());
  793. // Now this is our test
  794. RefRename renameRef = db.renameRef("refs/heads/prefix/a",
  795. "refs/heads/prefix");
  796. Result result = renameRef.rename();
  797. assertEquals(Result.RENAMED, result);
  798. assertNull(db.resolve("refs/heads/prefix/a"));
  799. assertEquals(rb, db.resolve("refs/heads/prefix"));
  800. assertEquals(3, db.getReflogReader("prefix").getReverseEntries().size());
  801. assertEquals("Branch: renamed prefix/a to prefix", db.getReflogReader(
  802. "prefix").getReverseEntries().get(0).getComment());
  803. assertEquals("Just a message", db.getReflogReader("prefix")
  804. .getReverseEntries().get(1).getComment());
  805. assertEquals("Setup", db.getReflogReader("prefix").getReverseEntries()
  806. .get(2).getComment());
  807. assertEquals("Branch: renamed prefix/a to prefix", db.getReflogReader(
  808. "HEAD").getReverseEntries().get(0).getComment());
  809. }
  810. private static void writeReflog(Repository db, ObjectId newId, String msg,
  811. String refName) throws IOException {
  812. RefDirectory refs = (RefDirectory) db.getRefDatabase();
  813. RefDirectoryUpdate update = refs.newUpdate(refName, true);
  814. update.setNewObjectId(newId);
  815. refs.log(update, msg, true);
  816. }
  817. private static class SubclassedId extends ObjectId {
  818. SubclassedId(AnyObjectId src) {
  819. super(src);
  820. }
  821. }
  822. }